diff --git a/src/stl.rs b/src/stl.rs index 6063f0ba..b174236b 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -40,7 +40,7 @@ pub const LIB_ID_RGB_COMMIT: &str = "stl:ZMTVCU25-QDo98xR-wI91wcu-ydb7kui-QfZbF$n-0KDS2ow#tuna-safari-design"; /// Strict types id for the library providing data types for RGB consensus. pub const LIB_ID_RGB_LOGIC: &str = - "stl:bioTBozT-NqelHGE-SPbnpMA-XBNSbXZ-6X0dANE-WHVirL8#explain-marvin-bless"; + "stl:IKFjGiNg-4MC4DKp-6XBvG0D-FCMXfqx-OnKhuRH-nb75Mcs#sector-season-anatomy"; fn _rgb_commit_stl() -> Result { LibBuilder::new(libname!(LIB_NAME_RGB_COMMIT), tiny_bset! { diff --git a/src/vm/contract.rs b/src/vm/contract.rs index 9bf0e2f5..e0d5534f 100644 --- a/src/vm/contract.rs +++ b/src/vm/contract.rs @@ -23,7 +23,7 @@ use std::borrow::Borrow; use std::cell::RefCell; use std::cmp::Ordering; -use std::fmt::Debug; +use std::fmt::{self, Debug, Display, Formatter}; use std::num::NonZeroU32; use std::rc::Rc; @@ -31,6 +31,7 @@ use amplify::confinement; use amplify::num::u24; use bp::seals::txout::{CloseMethod, ExplicitSeal, VerifyError, Witness}; use bp::{dbc, Tx, Txid}; +use chrono::{MappedLocalTime, TimeZone, Utc}; use commit_verify::mpc; use single_use_seals::SealWitness; use strict_encoding::{StrictDecode, StrictDumb, StrictEncode}; @@ -38,8 +39,8 @@ use strict_encoding::{StrictDecode, StrictDumb, StrictEncode}; use crate::{ AssetTags, AssignmentType, Assignments, AssignmentsRef, AttachState, ContractId, DataState, ExposedSeal, Extension, ExtensionType, FungibleState, Genesis, GlobalState, GlobalStateType, - GraphSeal, Impossible, Inputs, Metadata, OpFullType, OpId, OpType, Operation, Transition, - TransitionType, TxoSeal, TypedAssigns, Valencies, XChain, XOutpoint, XOutputSeal, + GraphSeal, Impossible, Inputs, Layer1, Metadata, OpFullType, OpId, OpType, Operation, + Transition, TransitionType, TxoSeal, TypedAssigns, Valencies, XChain, XOutpoint, XOutputSeal, LIB_NAME_RGB_LOGIC, }; @@ -290,29 +291,72 @@ impl<'op> Operation for OrdOpRef<'op> { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[derive(Getters, Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_LOGIC)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] -#[display("{height}@{timestamp}")] pub struct WitnessPos { - height: u32, + #[getter(as_copy)] + layer1: Layer1, + + // TODO: Move BlockHeight from bp-wallet to bp-consensus and use it here + #[getter(as_copy)] + height: NonZeroU32, + + #[getter(as_copy)] timestamp: i64, } +impl StrictDumb for WitnessPos { + fn strict_dumb() -> Self { + Self { + layer1: Layer1::Bitcoin, + height: NonZeroU32::MIN, + timestamp: 1231006505, + } + } +} + +// Sat Jan 03 18:15:05 2009 UTC +const BITCOIN_GENESIS_TIMESTAMP: i64 = 1231006505; + +// Sat Jan 03 18:15:05 2009 UTC +const LIQUID_GENESIS_TIMESTAMP: i64 = 1296692202; + impl WitnessPos { - pub fn new(height: u32, timestamp: i64) -> Option { - if height == 0 || timestamp < 1231006505 { + #[deprecated( + since = "0.11.0-beta.9", + note = "please use `WitnessPos::bitcoin` or `WitnessPos::liquid` instead" + )] + pub fn new(height: NonZeroU32, timestamp: i64) -> Option { + Self::bitcoin(height, timestamp) + } + + pub fn bitcoin(height: NonZeroU32, timestamp: i64) -> Option { + if timestamp < BITCOIN_GENESIS_TIMESTAMP { return None; } - Some(WitnessPos { height, timestamp }) + Some(WitnessPos { + layer1: Layer1::Bitcoin, + height, + timestamp, + }) } - pub fn height(&self) -> NonZeroU32 { NonZeroU32::new(self.height).expect("invariant") } + pub fn liquid(height: NonZeroU32, timestamp: i64) -> Option { + if timestamp < LIQUID_GENESIS_TIMESTAMP { + return None; + } + Some(WitnessPos { + layer1: Layer1::Liquid, + height, + timestamp, + }) + } } impl PartialOrd for WitnessPos { @@ -327,7 +371,31 @@ impl Ord for WitnessPos { fn cmp(&self, other: &Self) -> Ordering { assert!(self.timestamp > 0); assert!(other.timestamp > 0); - self.timestamp.cmp(&other.timestamp) + const BLOCK_TIME: i64 = 10 /*min*/ * 60 /*secs*/; + match (self.layer1, other.layer1) { + (a, b) if a == b => self.height.cmp(&other.height), + (Layer1::Bitcoin, Layer1::Liquid) + if (self.timestamp - other.timestamp).abs() < BLOCK_TIME => + { + Ordering::Greater + } + (Layer1::Liquid, Layer1::Bitcoin) + if (other.timestamp - self.timestamp).abs() < BLOCK_TIME => + { + Ordering::Less + } + _ => self.timestamp.cmp(&other.timestamp), + } + } +} + +impl Display for WitnessPos { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}, ", self.layer1, self.height)?; + match Utc.timestamp_opt(self.timestamp, 0) { + MappedLocalTime::Single(time) => write!(f, "{}", time.format("%Y-%m-%d %H:%M:%S")), + _ => f.write_str("invalid timestamp"), + } } } @@ -672,3 +740,31 @@ impl<'op> OpInfo<'op> { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn witness_post_timestamp() { + assert_eq!(WitnessPos::bitcoin(NonZeroU32::MIN, BITCOIN_GENESIS_TIMESTAMP - 1), None); + assert_eq!(WitnessPos::liquid(NonZeroU32::MIN, LIQUID_GENESIS_TIMESTAMP - 1), None); + assert_eq!(WitnessPos::liquid(NonZeroU32::MIN, BITCOIN_GENESIS_TIMESTAMP), None); + assert!(WitnessPos::bitcoin(NonZeroU32::MIN, BITCOIN_GENESIS_TIMESTAMP).is_some()); + assert!(WitnessPos::liquid(NonZeroU32::MIN, LIQUID_GENESIS_TIMESTAMP).is_some()); + assert!(WitnessPos::bitcoin(NonZeroU32::MIN, LIQUID_GENESIS_TIMESTAMP).is_some()); + } + + #[test] + fn witness_pos_getters() { + let pos = WitnessPos::bitcoin(NonZeroU32::MIN, BITCOIN_GENESIS_TIMESTAMP).unwrap(); + assert_eq!(pos.height(), NonZeroU32::MIN); + assert_eq!(pos.timestamp(), BITCOIN_GENESIS_TIMESTAMP); + assert_eq!(pos.layer1(), Layer1::Bitcoin); + + let pos = WitnessPos::liquid(NonZeroU32::MIN, LIQUID_GENESIS_TIMESTAMP).unwrap(); + assert_eq!(pos.height(), NonZeroU32::MIN); + assert_eq!(pos.timestamp(), LIQUID_GENESIS_TIMESTAMP); + assert_eq!(pos.layer1(), Layer1::Liquid); + } +}