bdk_wallet 3.0.0

A modern, lightweight, descriptor-based wallet library
Documentation
/// Test to demonstrate the RelLockTime issue in dsl.rs
///
/// The issue: older() macro uses .expect() on RelLockTime::from_consensus(),
/// which can panic if given an invalid value.
///
/// This file shows the problem and how to catch it.
use bdk_wallet::descriptor;
use miniscript::AbsLockTime;

#[test]
fn test_older_with_invalid_rellocktime_too_large() {
    // Value with high bit set causes RelLockTime::from_consensus() to fail
    let invalid_value = 0x80000000; // 2147483648

    // This should now return an error instead of panicking
    let result = descriptor!(wsh(older(invalid_value)));
    assert!(
        result.is_err(),
        "Invalid RelLockTime {} should return an error",
        invalid_value
    );

    // Check that it's the right kind of error
    if let Err(descriptor::DescriptorError::Miniscript(miniscript::Error::RelativeLockTime(_))) =
        result
    {
    } else {
        panic!("Expected RelLockTime error, got {:?}", result);
    }
}

#[test]
fn test_older_with_valid_rellocktime_max() {
    // Max valid value is 65,535 (0xFFFF)
    let max_valid_value = 65_535;
    let result = descriptor!(wsh(older(max_valid_value)));
    assert!(result.is_ok(), "Max valid RelLockTime should work");
}

#[test]
fn test_older_with_valid_rellocktime_min() {
    // Min valid value is 1 (0 is not valid for RelLockTime)
    let min_value = 1;

    let result = descriptor!(wsh(older(min_value)));
    assert!(result.is_ok(), "Min valid RelLockTime should work");
}

#[test]
fn test_older_with_valid_common_values() {
    // Common usage: blocks or seconds
    let test_cases = vec![
        1,         // 1 block/second
        144,       // ~1 day in blocks
        1000,      // Common value
        65535,     // Max 16-bit value (common for CSV)
        4_209_713, // Valid but large
    ];

    for value in test_cases {
        let result = descriptor!(wsh(older(value)));
        assert!(result.is_ok(), "Valid RelLockTime {} should work", value);
    }
}

// Alternative: Show how proper error handling should look
#[test]
fn test_demonstrate_proper_error_handling() {
    // This is what the fix should look like - proper Result handling
    // instead of .expect() which panics

    use miniscript::RelLockTime;

    // Valid case
    let valid_lock = RelLockTime::from_consensus(144);
    assert!(
        valid_lock.is_ok(),
        "144 blocks should be a valid RelLockTime"
    );

    // Invalid case
    let invalid_value = 0x8000_0000;
    let invalid_result = RelLockTime::from_consensus(invalid_value);
    assert!(
        invalid_result.is_err(),
        "Value 0x{:x} should return a RelLockTime error",
        invalid_value
    );
}

#[test]
fn test_rel_lock_time_error() {
    // 1. Create the underlying RelLockTime error (high bit set)
    let invalid_value = 0x80000000u32;
    let rel_lock_time_result = miniscript::RelLockTime::from_consensus(invalid_value);
    assert!(rel_lock_time_result.is_err());

    let rel_lock_time_error = rel_lock_time_result.unwrap_err();

    // 2. Wrap it in the Miniscript error variant (matching your new macro logic)
    let minisc_err = miniscript::Error::RelativeLockTime(rel_lock_time_error);

    // 3. Test the From impl (Miniscript -> DescriptorError)
    let error: descriptor::DescriptorError = minisc_err.into();

    // Check that it matches the nested structure
    assert!(matches!(
        error,
        descriptor::DescriptorError::Miniscript(miniscript::Error::RelativeLockTime(_))
    ));

    // 4. Test the Display impl
    let display_string = format!("{}", error);

    assert!(
        display_string.contains("relative locktime"),
        "Display string was: '{}'",
        display_string
    );
}

#[test]
fn test_after_with_invalid_abslocktime_zero() {
    let invalid_value = 0;

    let result = descriptor!(wsh(after(invalid_value)));

    assert!(
        result.is_err(),
        "AbsLockTime of 0 should return an error in this context"
    );

    // Check for the correct error variant
    if let Err(descriptor::DescriptorError::Miniscript(miniscript::Error::AbsoluteLockTime(_))) =
        result
    {
        // Success: Error was caught and mapped correctly
    } else {
        panic!("Expected AbsLockTime error, got {:?}", result);
    }
}

#[test]
fn test_after_with_valid_height() {
    // Values < 500,000,000 are interpreted as block heights
    let block_height = 840_000; // Example: Halving block height
    let result = descriptor!(wsh(after(block_height)));
    assert!(result.is_ok(), "Valid block height should work");
}

#[test]
fn test_after_with_valid_timestamp() {
    // Values >= 500,000,000 are interpreted as Unix timestamps
    let timestamp = 1715817600; // Example: A date in 2024
    let result = descriptor!(wsh(after(timestamp)));
    assert!(result.is_ok(), "Valid Unix timestamp should work");
}

#[test]
fn test_after_with_exact_max_valid_timestamp() {
    // 2,147,483,648 is the absolute maximum value supported
    // by this implementation of AbsLockTime.
    let max_valid = 2_147_483_648;
    let result = descriptor!(wsh(after(max_valid)));
    assert!(
        result.is_ok(),
        "Value 2,147,483,648 should be the valid upper bound"
    );
}

#[test]
fn test_after_with_invalid_just_above_max() {
    // 2,147,483,649 is too large for the internal representation
    let too_large = 2_147_483_649;
    let result = descriptor!(wsh(after(too_large)));

    assert!(
        matches!(
            result,
            Err(descriptor::DescriptorError::Miniscript(
                miniscript::Error::AbsoluteLockTime(_)
            ))
        ),
        "Should fail specifically with a Miniscript AbsoluteLockTime error"
    );
}

#[test]
fn test_after_with_u32_max_is_invalid() {
    // Verify that u32::MAX actually triggers the error you're looking for.
    let max_value = u32::MAX;
    let result = descriptor!(wsh(after(max_value)));

    assert!(
        result.is_err(),
        "u32::MAX is often rejected as an invalid consensus locktime"
    );
}

#[test]
fn test_abs_lock_time_error_mapping() {
    let invalid_value = 0;
    let abs_lock_result = AbsLockTime::from_consensus(invalid_value);

    assert!(abs_lock_result.is_err());
    let abs_err = abs_lock_result.unwrap_err();

    // Wrap in the general Miniscript Error enum
    let minisc_err = miniscript::Error::AbsoluteLockTime(abs_err);

    // Convert to your local Error type using .into()
    let error: descriptor::DescriptorError = minisc_err.into();

    // Assert it landed in the Miniscript variant
    assert!(matches!(error, descriptor::DescriptorError::Miniscript(_)));

    // Verify the error message contains the expected text
    let display_string = format!("{}", error);
    assert!(
        display_string.contains("absolute locktime"),
        "Error message '{}' should mention locktime",
        display_string
    );
}