java-topology/defects/wasabi-0002/test/wasabi-0002-test.cs

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);
}
}