108 lines
4.2 KiB
C#
108 lines
4.2 KiB
C#
// Unit test: wasabi-0002 TransactionFactory AllowedInputs O(C*A) List.Any -> HashSet
|
|
// Validates that HashSet-based filtering produces identical results.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
public class Wasabi0002Test
|
|
{
|
|
record FakeOutPoint(int Hash, int N);
|
|
record FakeCoin(int TransactionId, int Index, string ScriptPubKey, bool Confirmed)
|
|
{
|
|
public FakeOutPoint Outpoint => new(TransactionId, Index);
|
|
}
|
|
|
|
static int ComparisonCount = 0;
|
|
|
|
/// <summary>Defective: List.Any for AllowedInputs check</summary>
|
|
static List<FakeCoin> DefectiveFilter(List<FakeCoin> coins, List<FakeOutPoint> allowedInputs)
|
|
{
|
|
ComparisonCount = 0;
|
|
return coins.Where(x =>
|
|
allowedInputs.Any(y => { ComparisonCount++; return y.Hash == x.TransactionId && y.N == x.Index; }))
|
|
.ToList();
|
|
}
|
|
|
|
/// <summary>Patched: HashSet for AllowedInputs check</summary>
|
|
static List<FakeCoin> PatchedFilter(List<FakeCoin> coins, List<FakeOutPoint> allowedInputs)
|
|
{
|
|
ComparisonCount = 0;
|
|
var allowedSet = new HashSet<FakeOutPoint>(allowedInputs);
|
|
return coins.Where(x => { ComparisonCount++; return allowedSet.Contains(x.Outpoint); })
|
|
.ToList();
|
|
}
|
|
|
|
public static void Main()
|
|
{
|
|
int passed = 0;
|
|
int failed = 0;
|
|
|
|
// Test 1: Correctness
|
|
{
|
|
var coins = Enumerable.Range(0, 100).Select(i =>
|
|
new FakeCoin(i, 0, $"script_{i}", true)).ToList();
|
|
var allowed = Enumerable.Range(0, 50).Select(i =>
|
|
new FakeOutPoint(i * 2, 0)).ToList(); // even-indexed
|
|
|
|
var defective = DefectiveFilter(coins, allowed);
|
|
var patched = PatchedFilter(coins, allowed);
|
|
|
|
bool same = defective.Select(c => c.TransactionId).SequenceEqual(patched.Select(c => c.TransactionId));
|
|
if (same && defective.Count == 50)
|
|
{ Console.WriteLine("PASS test1_correctness: identical filter results"); passed++; }
|
|
else { Console.WriteLine("FAIL test1_correctness"); failed++; }
|
|
}
|
|
|
|
// Test 2: Performance at C=500, A=50
|
|
{
|
|
var coins = Enumerable.Range(0, 500).Select(i =>
|
|
new FakeCoin(i, 0, $"script_{i}", true)).ToList();
|
|
var allowed = Enumerable.Range(0, 50).Select(i =>
|
|
new FakeOutPoint(i * 10, 0)).ToList();
|
|
|
|
DefectiveFilter(coins, allowed);
|
|
int defectiveComps = ComparisonCount;
|
|
|
|
PatchedFilter(coins, allowed);
|
|
int patchedComps = ComparisonCount;
|
|
|
|
double ratio = (double)defectiveComps / patchedComps;
|
|
Console.WriteLine($" C=500 A=50: defective={defectiveComps} patched={patchedComps} ratio={ratio:F1}x");
|
|
|
|
if (ratio > 10.0) { Console.WriteLine("PASS test2_perf: >10x fewer comparisons"); passed++; }
|
|
else { Console.WriteLine($"FAIL test2_perf: ratio {ratio:F1}x not >10x"); failed++; }
|
|
}
|
|
|
|
// Test 3: Empty allowed inputs returns empty
|
|
{
|
|
var coins = Enumerable.Range(0, 10).Select(i =>
|
|
new FakeCoin(i, 0, $"script_{i}", true)).ToList();
|
|
var allowed = new List<FakeOutPoint>();
|
|
|
|
var defective = DefectiveFilter(coins, allowed);
|
|
var patched = PatchedFilter(coins, allowed);
|
|
|
|
if (defective.Count == 0 && patched.Count == 0)
|
|
{ Console.WriteLine("PASS test3_empty_allowed: returns empty"); passed++; }
|
|
else { Console.WriteLine("FAIL test3_empty_allowed"); failed++; }
|
|
}
|
|
|
|
// Test 4: All coins allowed
|
|
{
|
|
var coins = Enumerable.Range(0, 20).Select(i =>
|
|
new FakeCoin(i, 0, $"script_{i}", true)).ToList();
|
|
var allowed = coins.Select(c => c.Outpoint).ToList();
|
|
|
|
var defective = DefectiveFilter(coins, allowed);
|
|
var patched = PatchedFilter(coins, allowed);
|
|
|
|
if (defective.Count == 20 && patched.Count == 20)
|
|
{ Console.WriteLine("PASS test4_all_allowed: all coins pass"); passed++; }
|
|
else { Console.WriteLine("FAIL test4_all_allowed"); failed++; }
|
|
}
|
|
|
|
Console.WriteLine($"\n{passed}/{passed + failed} tests passed");
|
|
if (failed > 0) Environment.Exit(1);
|
|
}
|
|
}
|