Skip to content

[flame_manager] Greedy flame selector can emit an unbounded number of flames + sum-loop overflow #12

Description

@GideonBature

Summary

Two compounding issues in FlameManager::apply_changes's funding path:

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions