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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions