Skip to content

Commit 1916ea7

Browse files
Add PSBTVersion to CreatePSBT (#510)
1 parent e430314 commit 1916ea7

File tree

6 files changed

+65
-23
lines changed

6 files changed

+65
-23
lines changed

NBXplorer.Client/Models/CreatePSBTRequest.cs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ namespace NBXplorer.Models
88
{
99
public class CreatePSBTRequest
1010
{
11+
[JsonProperty("PSBTVersion")]
12+
public int? PSBTVersion { get; set; }
1113
/// <summary>
1214
/// A seed to specific to get a deterministic PSBT (useful for tests)
1315
/// </summary>

NBXplorer.Client/NBXplorer.Client.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
<NoWarn>$(NoWarn);1591;1573;1572;1584;1570;3021</NoWarn>
2828
</PropertyGroup>
2929
<ItemGroup>
30-
<PackageReference Include="NBitcoin" Version="7.0.50" />
31-
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.34" />
30+
<PackageReference Include="NBitcoin" Version="8.0.3" />
31+
<PackageReference Include="NBitcoin.Altcoins" Version="4.0.3" />
3232
</ItemGroup>
3333
<ItemGroup>
3434
<None Include="..\README.md" Pack="true" PackagePath="\" />

NBXplorer.Tests/NBXplorer.Tests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<EmbeddedResource Include="Scripts\generate-whale.sql" />
1212
</ItemGroup>
1313
<ItemGroup>
14-
<PackageReference Include="NBitcoin.TestFramework" Version="3.0.33" />
14+
<PackageReference Include="NBitcoin.TestFramework" Version="3.0.36" />
1515
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
1616
<PackageReference Include="xunit" Version="2.9.2" />
1717
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">

NBXplorer.Tests/UnitTest1.cs

+43-14
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,12 @@ public async Task CanEasilySpendUTXOs()
338338
}
339339
}
340340

341-
[FactWithTimeout]
342-
public async Task CanCreatePSBT()
341+
[TheoryWithTimeout]
342+
[InlineData(PSBTVersion.PSBTv0)]
343+
[InlineData(PSBTVersion.PSBTv2)]
344+
public async Task CanCreatePSBT(PSBTVersion v)
343345
{
346+
var version = v == PSBTVersion.PSBTv0 ? 0 : 2;
344347
using (var tester = ServerTester.Create())
345348
{
346349
// We need to check if we can get utxo information of segwit utxos
@@ -359,15 +362,15 @@ public async Task CanCreatePSBT()
359362
.BuildTransaction(false);
360363
var spendingPSBT = (await tester.Client.UpdatePSBTAsync(new UpdatePSBTRequest()
361364
{
362-
PSBT = PSBT.FromTransaction(spending, tester.Network)
365+
PSBT = PSBT.FromTransaction(spending, tester.Network, v)
363366
})).PSBT;
364367
Assert.NotNull(spendingPSBT.Inputs[0].WitnessUtxo);
365368
///////////////////////////
366369

367-
CanCreatePSBTCore(tester, ScriptPubKeyType.SegwitP2SH);
368-
CanCreatePSBTCore(tester, ScriptPubKeyType.Segwit);
369-
CanCreatePSBTCore(tester, ScriptPubKeyType.Legacy);
370-
CanCreatePSBTCore(tester, ScriptPubKeyType.TaprootBIP86);
370+
CanCreatePSBTCore(tester, version, ScriptPubKeyType.SegwitP2SH);
371+
CanCreatePSBTCore(tester, version, ScriptPubKeyType.Segwit);
372+
CanCreatePSBTCore(tester, version, ScriptPubKeyType.Legacy);
373+
CanCreatePSBTCore(tester, version, ScriptPubKeyType.TaprootBIP86);
371374

372375
// If we build a list of unconf transaction which is too long, the CreatePSBT should
373376
// fail rather than create a transaction that can't be broadcasted.
@@ -389,6 +392,7 @@ public async Task CanCreatePSBT()
389392
{
390393
var psbt = await tester.Client.CreatePSBTAsync(userDerivationScheme, new CreatePSBTRequest()
391394
{
395+
PSBTVersion = version,
392396
Destinations = {
393397
new CreatePSBTDestination()
394398
{
@@ -415,7 +419,7 @@ public async Task CanCreatePSBT()
415419
}
416420
}
417421

418-
private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type)
422+
private static void CanCreatePSBTCore(ServerTester tester, int psbtVersion, ScriptPubKeyType type)
419423
{
420424
var userExtKey = new ExtKey();
421425
var userDerivationScheme = tester.Client.Network.DerivationStrategyFactory.CreateDirectDerivationStrategy(userExtKey.Neuter(), new DerivationStrategyOptions()
@@ -447,6 +451,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
447451
{
448452
var req = new CreatePSBTRequest()
449453
{
454+
PSBTVersion = psbtVersion,
450455
Destinations =
451456
{
452457
new CreatePSBTDestination()
@@ -487,6 +492,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
487492
var explicitFee = i == 2;
488493
var psbt = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
489494
{
495+
PSBTVersion = psbtVersion,
490496
Destinations =
491497
{
492498
new CreatePSBTDestination()
@@ -525,6 +531,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
525531
var balance = tester.Client.GetUTXOs(userDerivationScheme).GetUnspentCoins().Select(c => c.Amount).Sum();
526532
var psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
527533
{
534+
PSBTVersion = psbtVersion,
528535
Destinations =
529536
{
530537
new CreatePSBTDestination()
@@ -544,6 +551,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
544551
Logs.Tester.LogInformation("Let's check that if ReserveChangeAddress is false, all call to CreatePSBT send the same change address");
545552
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
546553
{
554+
PSBTVersion = psbtVersion,
547555
Destinations =
548556
{
549557
new CreatePSBTDestination()
@@ -562,6 +570,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
562570

563571
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
564572
{
573+
PSBTVersion = psbtVersion,
565574
Destinations =
566575
{
567576
new CreatePSBTDestination()
@@ -580,6 +589,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
580589
Logs.Tester.LogInformation("Let's check that if ReserveChangeAddress is true, next call to CreatePSBT will create a new change address");
581590
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
582591
{
592+
PSBTVersion = psbtVersion,
583593
Destinations =
584594
{
585595
new CreatePSBTDestination()
@@ -598,6 +608,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
598608
var dest = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, tester.Network);
599609
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
600610
{
611+
PSBTVersion = psbtVersion,
601612
RBF = false,
602613
Seed = 0,
603614
Destinations =
@@ -620,6 +631,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
620631
Logs.Tester.LogInformation("Let's check that we can use the reserved change as explicit change and end up with the same psbt");
621632
var psbt3 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
622633
{
634+
PSBTVersion = psbtVersion,
623635
RBF = false,
624636
Seed = 0,
625637
Destinations =
@@ -641,6 +653,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
641653
Logs.Tester.LogInformation("Let's change that if ReserveChangeAddress is true, but the transaction fails to build, no address get reserverd");
642654
var ex = Assert.Throws<NBXplorerException>(() => psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
643655
{
656+
PSBTVersion = psbtVersion,
644657
Destinations =
645658
{
646659
new CreatePSBTDestination()
@@ -655,10 +668,11 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
655668
},
656669
ReserveChangeAddress = true
657670
}));
658-
Assert.False(psbt2.PSBT.GetOriginalTransaction().RBF);
671+
Assert.False(psbt2.PSBT.GetGlobalTransaction().RBF);
659672
Assert.Equal("not-enough-funds", ex.Error.Code);
660673
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
661674
{
675+
PSBTVersion = psbtVersion,
662676
RBF = true,
663677
Destinations =
664678
{
@@ -674,7 +688,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
674688
},
675689
ReserveChangeAddress = false
676690
});
677-
Assert.True(psbt2.PSBT.GetOriginalTransaction().RBF);
691+
Assert.True(psbt2.PSBT.GetGlobalTransaction().RBF);
678692
Assert.Equal(changeAddress, psbt2.ChangeAddress);
679693
foreach (var input in psbt2.PSBT.GetGlobalTransaction().Inputs)
680694
{
@@ -685,6 +699,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
685699
Logs.Tester.LogInformation("We have no confirmation, so we should not have enough money if asking for min 1 conf");
686700
ex = Assert.Throws<NBXplorerException>(() => psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
687701
{
702+
PSBTVersion = psbtVersion,
688703
Destinations =
689704
{
690705
new CreatePSBTDestination()
@@ -707,6 +722,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
707722
tester.WaitSynchronized();
708723
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
709724
{
725+
PSBTVersion = psbtVersion,
710726
Destinations =
711727
{
712728
new CreatePSBTDestination()
@@ -730,6 +746,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
730746
tester.Notifications.WaitForTransaction(userDerivationScheme, txId);
731747
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
732748
{
749+
PSBTVersion = psbtVersion,
733750
Destinations =
734751
{
735752
new CreatePSBTDestination()
@@ -744,11 +761,12 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
744761
},
745762
ReserveChangeAddress = false
746763
});
747-
var outpoints = psbt2.PSBT.GetOriginalTransaction().Inputs.Select(i => i.PrevOut).ToArray();
764+
var outpoints = psbt2.PSBT.GetGlobalTransaction().Inputs.Select(i => i.PrevOut).ToArray();
748765
Assert.Equal(2, outpoints.Length);
749766

750767
var request = new CreatePSBTRequest()
751768
{
769+
PSBTVersion = psbtVersion,
752770
IncludeOnlyOutpoints = new List<OutPoint>() { outpoints[0] },
753771
Destinations =
754772
{
@@ -767,7 +785,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
767785
};
768786
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, request);
769787

770-
var actualOutpoints = psbt2.PSBT.GetOriginalTransaction().Inputs.Select(i => i.PrevOut).ToArray();
788+
var actualOutpoints = psbt2.PSBT.GetGlobalTransaction().Inputs.Select(i => i.PrevOut).ToArray();
771789
Assert.Single(actualOutpoints);
772790
Assert.Equal(outpoints[0], actualOutpoints[0]);
773791
request.MinValue = Money.Coins(0.1m);
@@ -776,6 +794,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
776794

777795
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
778796
{
797+
PSBTVersion = psbtVersion,
779798
ExcludeOutpoints = new List<OutPoint>() { outpoints[0] },
780799
Destinations =
781800
{
@@ -792,14 +811,15 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
792811
ReserveChangeAddress = false
793812
});
794813

795-
actualOutpoints = psbt2.PSBT.GetOriginalTransaction().Inputs.Select(i => i.PrevOut).ToArray();
814+
actualOutpoints = psbt2.PSBT.GetGlobalTransaction().Inputs.Select(i => i.PrevOut).ToArray();
796815
Assert.Single(actualOutpoints);
797816
Assert.Equal(outpoints[1], actualOutpoints[0]);
798817

799818
Logs.Tester.LogInformation("Let's check nLocktime and version");
800819

801820
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
802821
{
822+
PSBTVersion = psbtVersion,
803823
Version = 2,
804824
RBF = false,
805825
LockTime = new LockTime(1_000_000),
@@ -818,7 +838,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
818838
},
819839
ReserveChangeAddress = false
820840
});
821-
var txx = psbt2.PSBT.GetOriginalTransaction();
841+
var txx = psbt2.PSBT.GetGlobalTransaction();
822842
Assert.Equal(new LockTime(1_000_000), txx.LockTime);
823843
Assert.Equal(2U, txx.Version);
824844
Assert.False(txx.RBF);
@@ -831,6 +851,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
831851

832852
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
833853
{
854+
PSBTVersion = psbtVersion,
834855
Destinations =
835856
{
836857
new CreatePSBTDestination()
@@ -886,6 +907,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
886907
var rootHD = new HDFingerprint(new byte[] { 0x04, 0x01, 0x02, 0x04 });
887908
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
888909
{
910+
PSBTVersion = psbtVersion,
889911
Destinations =
890912
{
891913
new CreatePSBTDestination()
@@ -931,6 +953,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
931953
Logs.Tester.LogInformation("Let's check that if the explicit change is one of the destination, fee are calculated correctly");
932954
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
933955
{
956+
PSBTVersion = psbtVersion,
934957
Destinations =
935958
{
936959
new CreatePSBTDestination()
@@ -955,6 +978,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
955978
{
956979
ex = Assert.Throws<NBXplorerException>(() => tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
957980
{
981+
PSBTVersion = psbtVersion,
958982
Destinations =
959983
{
960984
new CreatePSBTDestination()
@@ -1004,6 +1028,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
10041028
Logs.Tester.LogInformation("Let's check that if we can create or update a psbt with non_witness_utxo filled even for segwit inputs");
10051029
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
10061030
{
1031+
PSBTVersion = psbtVersion,
10071032
Destinations =
10081033
{
10091034
new CreatePSBTDestination()
@@ -1029,6 +1054,7 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
10291054
// Can we send to no destination?
10301055
psbt2 = tester.Client.CreatePSBT(userDerivationScheme, new CreatePSBTRequest()
10311056
{
1057+
PSBTVersion = psbtVersion,
10321058
SpendAllMatchingOutpoints = true,
10331059
IncludeOnlyOutpoints = psbt2.PSBT.Inputs.Select(t => t.PrevOut).ToList(),
10341060
FeePreference = new FeePreference()
@@ -1037,6 +1063,8 @@ private static void CanCreatePSBTCore(ServerTester tester, ScriptPubKeyType type
10371063
},
10381064
AlwaysIncludeNonWitnessUTXO = true
10391065
});
1066+
var expectedPSBTVersion = psbtVersion == 0 ? PSBTVersion.PSBTv0 : PSBTVersion.PSBTv2;
1067+
Assert.Equal(expectedPSBTVersion, psbt2.PSBT.Version);
10401068
}
10411069

10421070
private static PSBTOutput AssertHasOutput(ScriptPubKeyType type, KeyPath keyPath, CreatePSBTResponse psbt2)
@@ -1163,6 +1191,7 @@ public async Task ShowRBFedTransaction4()
11631191
// b' shouldn't have any output belonging to our wallets.
11641192
var bp = b.Clone();
11651193
bp.Outputs[0].Value -= Money.Satoshis(5000); // Add some fee to bump the tx
1194+
bp.RemoveSignatures();
11661195
var psbt2 = PSBT.FromTransaction(bp, tester.Network);
11671196
psbt2.UpdateFrom(preSignedPsbt);
11681197
psbt2.SignAll(ScriptPubKeyType.Segwit, bobW.AccountHDKey, bobW.AccountKeyPath);

NBXplorer/Controllers/MainController.PSBT.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ public async Task<IActionResult> CreatePSBT(
2929
var network = trackedSourceContext.Network;
3030
CreatePSBTRequest request = network.ParseJObject<CreatePSBTRequest>(body);
3131

32+
var psbtVersion = request.PSBTVersion switch
33+
{
34+
2 => PSBTVersion.PSBTv2,
35+
_ => PSBTVersion.PSBTv0
36+
};
37+
3238
var repo = RepositoryProvider.GetRepository(network);
3339
var txBuilder = request.Seed is int s ? network.NBitcoinNetwork.CreateTransactionBuilder(s)
3440
: network.NBitcoinNetwork.CreateTransactionBuilder();
@@ -321,7 +327,7 @@ public async Task<IActionResult> CreatePSBT(
321327
}
322328
if (request.SpendAllMatchingOutpoints is true)
323329
txBuilder.SendAllRemainingToChange();
324-
psbt = txBuilder.BuildPSBT(false);
330+
psbt = txBuilder.BuildPSBT(false, psbtVersion);
325331
hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey);
326332
}
327333
catch (OutputTooSmallException ex) when (ex.Reason == OutputTooSmallException.ErrorType.TooSmallAfterSubtractedFee)
@@ -356,15 +362,15 @@ public async Task<IActionResult> CreatePSBT(
356362
{
357363
change = (derivation.ScriptPubKey, derivation.KeyPath);
358364
txBuilder.SetChange(change.ScriptPubKey);
359-
psbt = txBuilder.BuildPSBT(false);
365+
psbt = txBuilder.BuildPSBT(false, psbtVersion);
360366
}
361367
}
362368

363-
var tx = psbt.GetOriginalTransaction();
369+
var tx = psbt.GetGlobalTransaction();
364370
if (request.Version is uint v)
365371
tx.Version = v;
366372
txBuilder.SetSigningOptions(SigHash.All);
367-
psbt = txBuilder.CreatePSBTFrom(tx, false);
373+
psbt = txBuilder.CreatePSBTFrom(tx, psbtVersion, false);
368374

369375
var update = new UpdatePSBTRequest()
370376
{
@@ -481,7 +487,7 @@ private static async Task UpdateHDKeyPathsWitnessAndRedeem(UpdatePSBTRequest upd
481487
var keyInfosByScriptPubKey = new Dictionary<Script, KeyPathInformation>();
482488
var scriptPubKeys = update.PSBT.Outputs.OfType<PSBTCoin>().Concat(update.PSBT.Inputs)
483489
.Where(o => !o.HDKeyPaths.Any())
484-
.Select(o => o.GetCoin()?.ScriptPubKey)
490+
.Select(o => o.GetTxOut()?.ScriptPubKey)
485491
.Where(s => s != null).ToArray();
486492
foreach (var keyInfos in (await repo.GetKeyInformations(scriptPubKeys)))
487493
{
@@ -503,7 +509,7 @@ private static async Task UpdateHDKeyPathsWitnessAndRedeem(UpdatePSBTRequest upd
503509
List<Script> redeems = new List<Script>();
504510
foreach (var c in update.PSBT.Outputs.OfType<PSBTCoin>().Concat(update.PSBT.Inputs.Where(o => !o.IsFinalized())))
505511
{
506-
var script = c.GetCoin()?.ScriptPubKey;
512+
var script = c.GetTxOut()?.ScriptPubKey;
507513
if (script != null &&
508514
keyInfosByScriptPubKey.TryGetValue(script, out var keyInfo))
509515
{

NBXplorer/wwwroot/api.json

+5
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,11 @@
411411
"PSBTRequest": {
412412
"type": "object",
413413
"properties": {
414+
"PSBTVersion": {
415+
"type": "integer",
416+
"description": "Optional. The format of the PSBT. (0 or 2). Default is 1.",
417+
"default": 0
418+
},
414419
"seed": {
415420
"type": "integer",
416421
"description": "Optional. A seed specific to get a deterministic PSBT, useful for testing purposes. Default is null."

0 commit comments

Comments
 (0)