use core::{mem, fmt};
use core::cmp::{PartialOrd, Ordering};
use core::convert::TryFrom;
use core::str::FromStr;
use crate::error::ParseIntError;
use crate::parse;
use crate::consensus::encode::{self, Decodable, Encodable};
use crate::io::{self, Read, Write};
use crate::prelude::*;
use crate::internal_macros::write_err;
use crate::parse::impl_parse_str_through_int;
pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct PackedLockTime(pub u32);
impl PackedLockTime {
pub const ZERO: PackedLockTime = PackedLockTime(0);
#[inline]
pub fn to_u32(self) -> u32 {
self.0
}
}
impl fmt::Display for PackedLockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl Encodable for PackedLockTime {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(w)
}
}
impl Decodable for PackedLockTime {
#[inline]
fn consensus_decode<R: Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
u32::consensus_decode(r).map(PackedLockTime)
}
}
impl From<LockTime> for PackedLockTime {
fn from(n: LockTime) -> Self {
PackedLockTime(n.to_consensus_u32())
}
}
impl From<PackedLockTime> for LockTime {
fn from(n: PackedLockTime) -> Self {
LockTime::from_consensus(n.0)
}
}
impl From<&LockTime> for PackedLockTime {
fn from(n: &LockTime) -> Self {
PackedLockTime(n.to_consensus_u32())
}
}
impl From<&PackedLockTime> for LockTime {
fn from(n: &PackedLockTime) -> Self {
LockTime::from_consensus(n.0)
}
}
impl From<PackedLockTime> for u32 {
fn from(p: PackedLockTime) -> Self {
p.0
}
}
impl_parse_str_through_int!(PackedLockTime);
impl fmt::LowerHex for PackedLockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:x}", self.0)
}
}
impl fmt::UpperHex for PackedLockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:X}", self.0)
}
}
#[allow(clippy::derive_ord_xor_partial_ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub enum LockTime {
Blocks(Height),
Seconds(Time),
}
impl LockTime {
pub const ZERO: LockTime = LockTime::Blocks(Height(0));
#[inline]
pub fn from_consensus(n: u32) -> Self {
if is_block_height(n) {
Self::Blocks(Height::from_consensus(n).expect("n is valid"))
} else {
Self::Seconds(Time::from_consensus(n).expect("n is valid"))
}
}
#[inline]
pub fn from_height(n: u32) -> Result<Self, Error> {
let height = Height::from_consensus(n)?;
Ok(LockTime::Blocks(height))
}
#[inline]
pub fn from_time(n: u32) -> Result<Self, Error> {
let time = Time::from_consensus(n)?;
Ok(LockTime::Seconds(time))
}
#[inline]
pub fn is_same_unit(&self, other: LockTime) -> bool {
mem::discriminant(self) == mem::discriminant(&other)
}
#[inline]
pub fn is_block_height(&self) -> bool {
match *self {
LockTime::Blocks(_) => true,
LockTime::Seconds(_) => false,
}
}
#[inline]
pub fn is_block_time(&self) -> bool {
!self.is_block_height()
}
#[inline]
pub fn is_satisfied_by(&self, height: Height, time: Time) -> bool {
use LockTime::*;
match *self {
Blocks(n) => n <= height,
Seconds(n) => n <= time,
}
}
#[inline]
pub fn to_consensus_u32(self) -> u32 {
match self {
LockTime::Blocks(ref h) => h.to_consensus_u32(),
LockTime::Seconds(ref t) => t.to_consensus_u32(),
}
}
}
impl_parse_str_through_int!(LockTime, from_consensus);
impl From<Height> for LockTime {
fn from(h: Height) -> Self {
LockTime::Blocks(h)
}
}
impl From<Time> for LockTime {
fn from(t: Time) -> Self {
LockTime::Seconds(t)
}
}
impl PartialOrd for LockTime {
fn partial_cmp(&self, other: &LockTime) -> Option<Ordering> {
use LockTime::*;
match (*self, *other) {
(Blocks(ref a), Blocks(ref b)) => a.partial_cmp(b),
(Seconds(ref a), Seconds(ref b)) => a.partial_cmp(b),
(_, _) => None,
}
}
}
impl fmt::Display for LockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTime::*;
if f.alternate() {
match *self {
Blocks(ref h) => write!(f, "block-height {}", h),
Seconds(ref t) => write!(f, "block-time {} (seconds since epoch)", t),
}
} else {
match *self {
Blocks(ref h) => fmt::Display::fmt(h, f),
Seconds(ref t) => fmt::Display::fmt(t, f),
}
}
}
}
impl Encodable for LockTime {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let v = self.to_consensus_u32();
v.consensus_encode(w)
}
}
impl Decodable for LockTime {
#[inline]
fn consensus_decode<R: Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
u32::consensus_decode(r).map(LockTime::from_consensus)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Height(u32);
impl Height {
#[inline]
pub fn from_consensus(n: u32) -> Result<Height, Error> {
if is_block_height(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_height(n).into())
}
}
#[inline]
pub fn to_consensus_u32(self) -> u32 {
self.0
}
}
impl fmt::Display for Height {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for Height {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let n = parse::int(s)?;
Height::from_consensus(n)
}
}
impl TryFrom<&str> for Height {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let n = parse::int(s)?;
Height::from_consensus(n)
}
}
impl TryFrom<String> for Height {
type Error = Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
let n = parse::int(s)?;
Height::from_consensus(n)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Time(u32);
impl Time {
#[inline]
pub fn from_consensus(n: u32) -> Result<Time, Error> {
if is_block_time(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_time(n).into())
}
}
#[inline]
pub fn to_consensus_u32(self) -> u32 {
self.0
}
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for Time {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let n = parse::int(s)?;
Time::from_consensus(n)
}
}
impl TryFrom<&str> for Time {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let n = parse::int(s)?;
Time::from_consensus(n)
}
}
impl TryFrom<String> for Time {
type Error = Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
let n = parse::int(s)?;
Time::from_consensus(n)
}
}
fn is_block_height(n: u32) -> bool {
n < LOCK_TIME_THRESHOLD
}
fn is_block_time(n: u32) -> bool {
n >= LOCK_TIME_THRESHOLD
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Conversion(ConversionError),
Operation(OperationError),
Parse(ParseIntError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match *self {
Conversion(ref e) => write_err!(f, "error converting lock time value"; e),
Operation(ref e) => write_err!(f, "error during lock time operation"; e),
Parse(ref e) => write_err!(f, "failed to parse lock time from string"; e),
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use self::Error::*;
match *self {
Conversion(ref e) => Some(e),
Operation(ref e) => Some(e),
Parse(ref e) => Some(e),
}
}
}
impl From<ConversionError> for Error {
fn from(e: ConversionError) -> Self {
Error::Conversion(e)
}
}
impl From<OperationError> for Error {
fn from(e: OperationError) -> Self {
Error::Operation(e)
}
}
impl From<ParseIntError> for Error {
fn from(e: ParseIntError) -> Self {
Error::Parse(e)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ConversionError {
unit: LockTimeUnit,
input: u32,
}
impl ConversionError {
fn invalid_height(n: u32) -> Self {
Self {
unit: LockTimeUnit::Blocks,
input: n,
}
}
fn invalid_time(n: u32) -> Self {
Self {
unit: LockTimeUnit::Seconds,
input: n,
}
}
}
impl fmt::Display for ConversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid lock time value {}, {}", self.input, self.unit)
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for ConversionError {}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum LockTimeUnit {
Blocks,
Seconds,
}
impl fmt::Display for LockTimeUnit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTimeUnit::*;
match *self {
Blocks => write!(f, "expected lock-by-blockheight (must be < {})", LOCK_TIME_THRESHOLD),
Seconds => write!(f, "expected lock-by-blocktime (must be >= {})", LOCK_TIME_THRESHOLD),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum OperationError {
InvalidComparison,
}
impl fmt::Display for OperationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::OperationError::*;
match *self {
InvalidComparison => f.write_str("cannot compare different lock units (height vs time)"),
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for OperationError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_and_alternate() {
let n = LockTime::from_consensus(100);
let s = format!("{}", n);
assert_eq!(&s, "100");
let got = format!("{:#}", n);
assert_eq!(got, "block-height 100");
}
}