| | 1 | | namespace NLightning.Infrastructure.Bitcoin.Comparers; |
| | 2 | |
|
| | 3 | | using Outputs; |
| | 4 | |
|
| | 5 | | public class TransactionOutputComparer : IComparer<BaseOutput> |
| | 6 | | { |
| 168 | 7 | | public static TransactionOutputComparer Instance { get; } = new(); |
| | 8 | |
|
| | 9 | | public int Compare(BaseOutput? x, BaseOutput? y) |
| | 10 | | { |
| | 11 | | switch (x, y) |
| | 12 | | { |
| | 13 | | // Deal with nulls |
| | 14 | | case (null, null): |
| 4 | 15 | | return 0; |
| | 16 | | case (null, not null): |
| 4 | 17 | | return -1; |
| | 18 | | case (not null, null): |
| 4 | 19 | | return 1; |
| | 20 | | } |
| | 21 | |
|
| | 22 | | // Compare by value (satoshis) |
| 604 | 23 | | var valueComparison = x.Amount.CompareTo(y.Amount); |
| 604 | 24 | | if (valueComparison != 0) |
| | 25 | | { |
| 500 | 26 | | return valueComparison; |
| | 27 | | } |
| | 28 | |
|
| | 29 | | // Compare by scriptPubKey lexicographically |
| 104 | 30 | | var scriptComparison = CompareScriptPubKey(x.ScriptPubKey.ToBytes(), y.ScriptPubKey.ToBytes()); |
| 104 | 31 | | if (scriptComparison != 0) |
| | 32 | | { |
| 84 | 33 | | return scriptComparison; |
| | 34 | | } |
| | 35 | |
|
| | 36 | | // For HTLC outputs, compare by CLTV expiry |
| 20 | 37 | | if (x is OfferedHtlcOutput or ReceivedHtlcOutput && |
| 20 | 38 | | y is OfferedHtlcOutput or ReceivedHtlcOutput) |
| | 39 | | { |
| 20 | 40 | | ulong xExpiry = x switch |
| 20 | 41 | | { |
| 20 | 42 | | OfferedHtlcOutput offered => offered.CltvExpiry, |
| 0 | 43 | | ReceivedHtlcOutput received => received.CltvExpiry, |
| 0 | 44 | | _ => 0 |
| 20 | 45 | | }; |
| | 46 | |
|
| 20 | 47 | | ulong yExpiry = y switch |
| 20 | 48 | | { |
| 20 | 49 | | OfferedHtlcOutput offered => offered.CltvExpiry, |
| 0 | 50 | | ReceivedHtlcOutput received => received.CltvExpiry, |
| 0 | 51 | | _ => 0 |
| 20 | 52 | | }; |
| | 53 | |
|
| 20 | 54 | | if (xExpiry != yExpiry) |
| | 55 | | { |
| 20 | 56 | | return xExpiry.CompareTo(yExpiry); |
| | 57 | | } |
| | 58 | | } |
| | 59 | |
|
| 0 | 60 | | return 0; |
| | 61 | | } |
| | 62 | |
|
| | 63 | | private static int CompareScriptPubKey(ReadOnlySpan<byte> script1, ReadOnlySpan<byte> script2) |
| | 64 | | { |
| 104 | 65 | | var length = Math.Min(script1.Length, script2.Length); |
| 1992 | 66 | | for (var i = 0; i < length; i++) |
| | 67 | | { |
| 960 | 68 | | if (script1[i] != script2[i]) |
| | 69 | | { |
| 68 | 70 | | return script1[i].CompareTo(script2[i]); |
| | 71 | | } |
| | 72 | | } |
| | 73 | |
|
| | 74 | | // Compare by length if scripts are identical up to the length of the shorter one |
| 36 | 75 | | return script1.Length.CompareTo(script2.Length); |
| | 76 | | } |
| | 77 | | } |