bdk icon indicating copy to clipboard operation
bdk copied to clipboard

Update Balance immature category calculation to consider MTP-based maturity

Open evanlinjin opened this issue 3 months ago • 1 comments

Background

As noted in PR #2037 review, the Balance immature category calculation could be enhanced to consider transaction maturity based on both Locktime and Median Time Past (MTP).

Prerequisites

  • Wait for PR #2029 to be merged first (simplifies API surface)
  • PR #2037 should be merged (adds MTP calculation to CheckPoint)

Task

Update the Balance struct's immature category calculation to properly consider transaction maturity using:

  1. Transaction locktime
  2. Median Time Past (MTP) from the CheckPoint

Implementation Notes

  • The MTP calculation is now available in CheckPoint (added in PR #2037)
  • Should integrate with the simplified API from PR #2029
  • Ensure proper testing with various locktime scenarios

Acceptance Criteria

  • [ ] Balance immature category correctly identifies transactions that are locked by time
  • [ ] MTP is properly used for time-based locktime comparisons
  • [ ] Existing tests pass
  • [ ] New tests added for MTP-based maturity checks

evanlinjin avatar Sep 19 '25 01:09 evanlinjin

Handling missing blocks/block-times

For calculating MTP-based maturity when some block timestamps are missing, implement a worst-case MTP approach:

Strategy

  1. Calculate worst-case MTP: Fill missing block timestamps with 0 (Unix epoch)
  2. Three-state maturity:
    • Mature: Locktime < worst_case_mtp (definitely spendable)
    • Unknown: Locktime >= worst_case_mtp (might be spendable with real timestamps)
    • Immature: Locktime > current time (definitely not spendable yet)

Implementation sketch

fn worst_case_mtp(checkpoint: &CheckPoint) -> u64 {
    let mut timestamps = Vec::new();
    
    for cp in checkpoint.iter().take(11) {
        timestamps.push(cp.timestamp.unwrap_or(0));
    }
    
    if timestamps.is_empty() {
        return 0; // No blocks = epoch time
    }
    
    timestamps.sort();
    timestamps[timestamps.len() / 2] // median
}

Why use 0 for missing timestamps?

  • Absolute guarantee: Real MTP can never be earlier than our calculation
  • Simple logic: No complex calculations or edge cases
  • Clear intent: Obviously represents unknown/worst-case
  • Safe for all networks: Works for mainnet, testnet, regtest

Return value

Always returns a value (never None):

  • Empty chain → 0
  • Fewer than 11 blocks → median of available blocks (padded with 0s)
  • 11+ blocks → proper median

This approach maximizes spendable balance while guaranteeing we never incorrectly mark time-locked funds as mature.

evanlinjin avatar Sep 19 '25 02:09 evanlinjin