Skip to content

Commit c8a3fd9

Browse files
authored
test(e2e): gate Fulmine liquidity on spendable VTXOs, recover via settle (#130)
EnsureArkLiquidity gated on Fulmine's raw /api/v1/balance, which also counts VTXOs that have passed their renewal deadline. On the regtest stack VTXOs live only ~50 minutes, so on any long-lived stack Fulmine's balance looks healthy while every offchain send fails with {"code":2,"message":"missing vtxos"} — and since SendArkdNoteTo funds nearly every E2E fixture through this helper, the whole suite collapses with instant InternalServerError failures while the helper keeps declaring liquidity sufficient. Gate on the spendable subset instead: sum /api/v1/vtxos entries that are not spent, not swept, and at least a minute from expiry (falling back to the raw balance if the endpoint is unavailable). Recovery is a settle — a settle round re-anchors expired/recoverable funds into a fresh spendable set — so only board fresh BTC when the wallet is genuinely underfunded rather than merely stale. Verified locally: healthy path 5x green (probe logs spendable=raw, immediate return); recovery path proven manually (fund boarding + /api/v1/settle restored a faucet stuck on "missing vtxos", after which the full E2E suite went 50/50 green).
1 parent 54eafee commit c8a3fd9

1 file changed

Lines changed: 64 additions & 14 deletions

File tree

NArk.Tests.End2End/Common/FulmineLiquidityHelper.cs

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,48 @@ namespace NArk.Tests.End2End.Common;
1010
public static class FulmineLiquidityHelper
1111
{
1212
/// <summary>
13-
/// Ensures Fulmine has enough ARK VTXOs by funding its boarding address, mining, and settling.
13+
/// Ensures Fulmine has enough *spendable* ARK VTXOs by settling (which renews
14+
/// expired/recoverable VTXOs) and, when genuinely underfunded, boarding fresh BTC first.
1415
/// Call this after Boltz is healthy but before tests that create BTC→ARK or reverse swaps.
1516
/// </summary>
17+
/// <remarks>
18+
/// The raw <c>/api/v1/balance</c> amount also counts VTXOs that have passed their
19+
/// renewal deadline — Fulmine reports them in the balance but a plain offchain send
20+
/// fails with <c>{"code":2,"message":"missing vtxos"}</c>. VTXOs on the regtest stack
21+
/// live only ~50 minutes, so a long-lived stack hits this constantly. Gate on the
22+
/// spendable subset (from <c>/api/v1/vtxos</c>) instead, and recover via settle:
23+
/// a settle round re-anchors the expired funds into a fresh spendable set.
24+
/// </remarks>
1625
public static async Task EnsureArkLiquidity(long minBalance = 200_000, int maxAttempts = 20)
1726
{
1827
var fulmineHttp = new HttpClient { BaseAddress = new Uri(SharedSwapInfrastructure.FulmineEndpoint.ToString()) };
1928

2029
for (var attempt = 0; attempt < maxAttempts; attempt++)
2130
{
22-
var arkBalance = await GetFulmineArkBalance(fulmineHttp);
23-
Console.WriteLine($"[FulmineLiquidity] ARK balance: {arkBalance} sats (attempt {attempt}, need {minBalance})");
24-
if (arkBalance >= minBalance) return;
25-
26-
// Fund Fulmine's boarding address with fresh BTC each attempt
27-
await FundFulmineBoarding(fulmineHttp);
31+
var rawBalance = await GetFulmineArkBalance(fulmineHttp);
32+
var spendable = await GetFulmineSpendableBalance(fulmineHttp);
33+
// Spendable probe is best-effort: if /api/v1/vtxos is unavailable, fall
34+
// back to the raw balance (the original behaviour).
35+
var effective = spendable >= 0 ? spendable : rawBalance;
36+
Console.WriteLine($"[FulmineLiquidity] ARK balance: spendable={spendable} raw={rawBalance} sats (attempt {attempt}, need {minBalance})");
37+
if (effective >= minBalance) return;
38+
39+
if (rawBalance < minBalance)
40+
{
41+
// Genuinely underfunded — board fresh BTC before settling.
42+
await FundFulmineBoarding(fulmineHttp);
2843

29-
// Mine blocks to confirm the boarding UTXO (arkd requires confirmed inputs)
30-
for (var i = 0; i < 6; i++)
31-
await DockerHelper.MineBlocks();
32-
await Task.Delay(TimeSpan.FromSeconds(2));
44+
// Mine blocks to confirm the boarding UTXO (arkd requires confirmed inputs)
45+
for (var i = 0; i < 6; i++)
46+
await DockerHelper.MineBlocks();
47+
await Task.Delay(TimeSpan.FromSeconds(2));
3348

34-
// Log raw balance after mining (to see if onchain increased)
35-
await LogRawBalance(fulmineHttp, "after-mine");
49+
// Log raw balance after mining (to see if onchain increased)
50+
await LogRawBalance(fulmineHttp, "after-mine");
51+
}
3652

37-
// Now settle — boarding UTXO is confirmed
53+
// Settle — renews expired/recoverable VTXOs into a fresh spendable set
54+
// and absorbs any just-boarded UTXO.
3855
await SettleWithLogging(fulmineHttp);
3956

4057
// Wait for the arkd batch round to process the settle intent
@@ -123,6 +140,39 @@ private static async Task FundFulmineBoarding(HttpClient fulmineHttp)
123140
}
124141
}
125142

143+
/// <summary>
144+
/// Sums Fulmine's *spendable* VTXOs in sats: not spent, not swept, and not
145+
/// within a minute of their renewal deadline (a near-expiry VTXO can lapse
146+
/// mid-flow). Returns -1 when the <c>/api/v1/vtxos</c> endpoint is
147+
/// unavailable or unparseable, so callers can fall back to the raw balance.
148+
/// </summary>
149+
private static async Task<long> GetFulmineSpendableBalance(HttpClient fulmineHttp)
150+
{
151+
try
152+
{
153+
var vtxosJson = await fulmineHttp.GetStringAsync("/api/v1/vtxos");
154+
var vtxos = JsonNode.Parse(vtxosJson)?["vtxos"]?.AsArray();
155+
if (vtxos is null) return -1;
156+
157+
var cutoff = DateTimeOffset.UtcNow.AddMinutes(1).ToUnixTimeSeconds();
158+
long spendable = 0;
159+
foreach (var vtxo in vtxos)
160+
{
161+
if (vtxo?["isSpent"]?.GetValue<bool>() == true) continue;
162+
if (vtxo?["isSwept"]?.GetValue<bool>() == true) continue;
163+
if (!long.TryParse(vtxo?["expiresAt"]?.ToString(), out var expiresAt) || expiresAt <= cutoff) continue;
164+
if (long.TryParse(vtxo?["amount"]?.ToString(), out var amount))
165+
spendable += amount;
166+
}
167+
return spendable;
168+
}
169+
catch (Exception ex)
170+
{
171+
Console.WriteLine($"[FulmineLiquidity] Spendable-balance check failed: {ex.Message}");
172+
return -1;
173+
}
174+
}
175+
126176
/// <summary>
127177
/// Gets Fulmine's ARK VTXO balance in sats.
128178
/// </summary>

0 commit comments

Comments
 (0)