bdk
bdk copied to clipboard
Update Balance immature category calculation to consider MTP-based maturity
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:
- Transaction locktime
- 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
Handling missing blocks/block-times
For calculating MTP-based maturity when some block timestamps are missing, implement a worst-case MTP approach:
Strategy
- Calculate worst-case MTP: Fill missing block timestamps with 0 (Unix epoch)
- 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.