From transaction list to graph
MODIFY
WalletWasabi.Tests/UnitTests/NBitcoinTests.cs
WalletWasabi.Tests/UnitTests/NBitcoinTests.cs
using NBitcoin;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Text;
using Xunit;
@@ -18,5 +19,57 @@ namespace WalletWasabi.Tests.UnitTests
Assert.Equal(WalletWasabi.Helpers.Constants.DefaultTestNetBitcoinP2pPort, Network.TestNet.DefaultPort);
Assert.Equal(WalletWasabi.Helpers.Constants.DefaultRegTestBitcoinP2pPort, Network.RegTest.DefaultPort);
}
+
+ [Fact]
+ public void DepencencyTransactionsGraph()
+ {
+ // tx0 -----+
+ // |
+ // +----+---> tx3
+ // | |
+ // tx1 -----+ +---> tx4 ---> tx5 ---> tx6 ----+
+ // |
+ // +---> tx7
+ // |
+ // tx2 ------------------------------------------+
+
+ var (tx0, c0) = CreateTransaction();
+ var (tx1, c1) = CreateTransaction();
+ var (tx2, c2) = CreateTransaction();
+
+ var (tx3, c3) = CreateTransaction(c0[0], c0[1], c1[0]);
+ var (tx4, c4) = CreateTransaction(c0[2], c1[2]);
+ var (tx5, c5) = CreateTransaction(c4[1], c4[2]);
+
+ var (tx6, c6) = CreateTransaction(c5[0]);
+ var (tx7, c7) = CreateTransaction(c6[2], c2[0]);
+
+ var graph = new[] { tx0, tx1, tx2, tx7, tx5, tx3, tx4, tx6 }.ToDependencyGraph();
+
+ Assert.Equal(3, graph.Count()); // tx0, tx1 and tx2
+ Assert.Equal(2, graph.First().Children.Count()); // tx0 has two children tx3 and tx4
+ Assert.Equal(2, graph.Skip(1).First().Children.Count()); // tx1 has two children tx3 and tx4
+ Assert.Equal(1, graph.Last().Children.Count()); // tx2 has only one children tx7
+ Assert.Equal(2, graph.Last().Children.Single().Parents.Count()); // tx7 has two parents tx2 and tx6
+ }
+
+ private static (Transaction, Coin[]) CreateTransaction(params Coin[] coins)
+ {
+ if( coins == null || coins.Length == 0)
+ {
+ coins = Enumerable.Range(0, 10).Select(_=>new Coin(RandomUtils.GetUInt256(), 0u, Money.Coins(10), Script.Empty)).ToArray();
+ }
+ var tx = Network.RegTest.CreateTransaction();
+ foreach (var coin in coins)
+ {
+ tx.Inputs.Add(coin.Outpoint, Script.Empty, WitScript.Empty);
+ }
+ tx.Outputs.Add(Money.Coins(3), Script.Empty);
+ tx.Outputs.Add(Money.Coins(2), Script.Empty);
+ tx.Outputs.Add(Money.Coins(1), Script.Empty);
+ tx.PrecomputeHash(true, false);
+ return (tx, tx.Outputs.AsCoins().ToArray());
+ }
+
}
}

MODIFY
WalletWasabi/Extensions/NBitcoinExtensions.cs
WalletWasabi/Extensions/NBitcoinExtensions.cs
@@ -314,5 +314,40 @@ namespace NBitcoin
{
return Money.Satoshis(Math.Round(me.SatoshiPerByte * vsize));
}
+
+ public class TransactionDependencyNode
+ {
+ public List<TransactionDependencyNode> Children = new List<TransactionDependencyNode>();
+ public List<TransactionDependencyNode> Parents = new List<TransactionDependencyNode>();
+ public Transaction Transaction { get; set; }
+ }
+
+ public static IEnumerable<TransactionDependencyNode> ToDependencyGraph(this IEnumerable<Transaction> txs)
+ {
+ var lookup = new Dictionary<uint256, TransactionDependencyNode>();
+ foreach(var tx in txs)
+ {
+ lookup.Add(tx.GetHash(), new TransactionDependencyNode { Transaction = tx });
+ }
+
+ foreach (var node in lookup.Values)
+ {
+ foreach(var input in node.Transaction.Inputs)
+ {
+ if (lookup.TryGetValue(input.PrevOut.Hash, out var parent))
+ {
+ if(!node.Parents.Contains(parent))
+ {
+ node.Parents.Add(parent);
+ }
+ if(!parent.Children.Contains(node))
+ {
+ parent.Children.Add(node);
+ }
+ }
+ }
+ }
+ return lookup.Values.Where(x => !x.Parents.Any());
+ }
}
}

From transaction list to graph