Summary
Two compounding issues in FlameManager::apply_changes's funding path:
- The greedy change-making selector in
return_flames_to_fund pushes one Flame per unit. When the gap is large and only a small denomination tier is configured, this allocates a Vec whose length is gap / min_tier_value.
- The running flame-set sum is accumulated with a plain
+ on u64, with no overflow check.
Either of these can turn a single account funding into a pathological allocation (millions of heap entries) or a silent wrap.
Affected file(s)
src/inscriptive/flame_manager/algorithms/flame_selection/flame_selection.rs (selector)
src/inscriptive/flame_manager/flame_manager.rs (sum loop + apply)
Location
1. Unbounded flame emission
src/inscriptive/flame_manager/algorithms/flame_selection/flame_selection.rs:126-145
let mut remaining_gap_amount = gap_amount;
for (tier_value, script_pubkey, flame_tier) in &available_tiers {
let count = remaining_gap_amount / tier_value;
if count > 0 {
for _ in 0..count { // <-- one Flame per unit
let flame = Flame::new(*flame_tier, script_pubkey.clone());
flames.push(flame);
}
remaining_gap_amount -= count * tier_value;
}
if remaining_gap_amount == 0 {
break;
}
}
If tier_any_amount is not configured and the smallest enabled tier is, say, Tier1HundredSatoshis (100 sats), a gap of 1 BTC (100,000,000 sats) produces 1,000,000 individually-allocated Flame values, each cloning the script pubkey. Each is then individually written to its own sled key in apply_changes (flame_manager.rs ~lines 438-450, key = [8-byte LE height][4-byte LE flame_index]).
2. Sum-loop overflow
src/inscriptive/flame_manager/flame_manager.rs (~lines 387-390), inside the per-account funding loop:
let mut account_current_flame_set_sum_value_in_satoshis: u64 = 0;
for (_flame_index, flame) in flames.iter() {
account_current_flame_set_sum_value_in_satoshis += flame.satoshi_amount(); // <-- plain add
}
No checked_add / saturating_add. Combined with (1), or simply with a large legitimate allocation set, this u64 can wrap, after which return_flames_to_fund computes a bogus gap = target - sum and floods the account with flames.
Root cause / analysis
The greedy algorithm is correct as math but does not coalesce identical-denomination flames. The flame_index space (a u32 per (account, height)) also bounds the total to 4 billion, but the practical limit is memory and sled key count long before that.
Impact
- Memory / DA blowup: an adversarial or pathological account config can force the node to materialize and persist millions of flame entries in one batch.
- Silent corruption: the sum-loop overflow can under-report the current funding, causing over- or under-funding that persists on disk.
- DoS-shaped: the work happens inside
apply_changes (a commit path), under the flame manager mutex.
Summary
Two compounding issues in
FlameManager::apply_changes's funding path:return_flames_to_fundpushes oneFlameper unit. When the gap is large and only a small denomination tier is configured, this allocates aVecwhose length isgap / min_tier_value.+onu64, with no overflow check.Either of these can turn a single account funding into a pathological allocation (millions of heap entries) or a silent wrap.
Affected file(s)
src/inscriptive/flame_manager/algorithms/flame_selection/flame_selection.rs(selector)src/inscriptive/flame_manager/flame_manager.rs(sum loop + apply)Location
1. Unbounded flame emission
src/inscriptive/flame_manager/algorithms/flame_selection/flame_selection.rs:126-145If
tier_any_amountis not configured and the smallest enabled tier is, say,Tier1HundredSatoshis(100 sats), a gap of 1 BTC (100,000,000 sats) produces 1,000,000 individually-allocatedFlamevalues, each cloning the script pubkey. Each is then individually written to its own sled key inapply_changes(flame_manager.rs~lines 438-450, key =[8-byte LE height][4-byte LE flame_index]).2. Sum-loop overflow
src/inscriptive/flame_manager/flame_manager.rs(~lines 387-390), inside the per-account funding loop:No
checked_add/saturating_add. Combined with (1), or simply with a large legitimate allocation set, thisu64can wrap, after whichreturn_flames_to_fundcomputes a bogusgap = target - sumand floods the account with flames.Root cause / analysis
The greedy algorithm is correct as math but does not coalesce identical-denomination flames. The
flame_indexspace (au32per(account, height)) also bounds the total to 4 billion, but the practical limit is memory and sled key count long before that.Impact
apply_changes(a commit path), under the flame manager mutex.