Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Time Locks

Sapio provides typed absolute and relative timelocks in sapio_base::timelocks. Heights and times have separate constructors so a value cannot silently change its interpretation.

use sapio_base::timelocks::*;
use sapio_base::Clause;
use std::convert::{TryFrom, TryInto};
use std::time::Duration;

fn main() -> Result<(), LockTimeError> {
let height = AbsHeight::try_from(800_000u32)?;
let timestamp = AbsTime::try_from(1_000_000_000u32)?;
let same_timestamp = AbsTime::try_from(Duration::from_secs(1_000_000_000))?;

// Relative time is encoded in intervals of 512 seconds.
let intervals = RelTime::from(10u16);
let duration = RelTime::try_from(Duration::from_secs(10 * 512))?;
let blocks = RelHeight::from(20u16);

// Converting a transaction timelock into a policy is fallible.
let older: Clause = blocks.try_into()?;
let after: Clause = height.try_into()?;

let relative: AnyRelTimeLock = blocks.into();
let any: AnyTimeLock = relative.into();
let also_older = Clause::try_from(any)?;
Ok(())
}

RelTime::try_from(Duration) rounds up to whole 512-second intervals, so a fractional interval cannot make the lock mature early. AbsTime rounds a fractional timestamp up to a whole second. Both reject durations outside their encoded range. The types’ JSON values are their encoded consensus fields; relative time includes the time-type flag, not just a count of seconds.

Transaction fields and policy guards

Transaction fields and Miniscript predicates have different valid domains. Sapio’s absolute transaction locks cover heights 0..500_000_000 and timestamps 500_000_000..=u32::MAX. Relative heights and time-interval counts fit in a u16. These types can be passed to the template builder without converting them into policy clauses.

Miniscript’s typed absolute guards accept encoded values 1..=0x7fff_ffff. Its relative guards require an enabled BIP68 encoding and a nonzero encoded operand. For example, a zero-block sequence is a valid transaction field, but cannot become an Older policy; a timestamp above 0x7fff_ffff also cannot become an After policy:

#![allow(unused)]
fn main() {
use sapio_base::timelocks::{AbsTime, RelHeight};
use sapio_base::Clause;

assert!(Clause::try_from(RelHeight::from(0u16)).is_err());
let timestamp = AbsTime::try_from(u32::MAX).unwrap();
assert!(Clause::try_from(timestamp).is_err());
}

Use Clause::try_from(lock) or lock.try_into() and propagate LockTimeError::InvalidPolicyLockTime when building a guard. The Any*TimeLock wrappers use the same fallible conversion. If constructing Miniscript clauses directly, use its typed lock constructors; Sapio also rejects the exposed RelLockTime::ZERO constant during policy validation.