From b22610a89e7b9081fa69a36b08eb6e7a3a232cf4 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 16 Mar 2024 17:57:06 +0100 Subject: [PATCH] feat: support external layer 1s --- src/contract/anchor.rs | 1 + src/contract/seal.rs | 15 ++- src/contract/xchain.rs | 233 ++++++++++++++++++++++++++++++++++------- 3 files changed, 210 insertions(+), 39 deletions(-) diff --git a/src/contract/anchor.rs b/src/contract/anchor.rs index 36a866c6..648598f4 100644 --- a/src/contract/anchor.rs +++ b/src/contract/anchor.rs @@ -71,6 +71,7 @@ impl XAnchor { pub fn known_bundle_ids(&self) -> impl Iterator + '_ { match self { XAnchor::Bitcoin(anchor) | XAnchor::Liquid(anchor) => anchor.known_bundle_ids(), + _ => unreachable!(), } } diff --git a/src/contract/seal.rs b/src/contract/seal.rs index e0ec0ca0..1cf2ac03 100644 --- a/src/contract/seal.rs +++ b/src/contract/seal.rs @@ -35,6 +35,7 @@ use commit_verify::{mpc, Conceal}; use single_use_seals::SealWitness; use strict_encoding::{StrictDecode, StrictDumb, StrictEncode, StrictType}; +use crate::contract::xchain::Impossible; use crate::{XChain, XOutpoint, LIB_NAME_RGB}; pub type GenesisSeal = SingleBlindSeal; @@ -70,36 +71,42 @@ impl TxoSeal for XChain { fn method(&self) -> CloseMethod { match self { XChain::Bitcoin(seal) | XChain::Liquid(seal) => seal.method(), + XChain::Other(_) => unreachable!(), } } fn txid(&self) -> Option { match self { XChain::Bitcoin(seal) | XChain::Liquid(seal) => seal.txid(), + XChain::Other(_) => unreachable!(), } } fn vout(&self) -> Vout { match self { XChain::Bitcoin(seal) | XChain::Liquid(seal) => seal.vout(), + XChain::Other(_) => unreachable!(), } } fn outpoint(&self) -> Option { match self { XChain::Bitcoin(seal) | XChain::Liquid(seal) => seal.outpoint(), + XChain::Other(_) => unreachable!(), } } fn txid_or(&self, default_txid: Txid) -> Txid { match self { XChain::Bitcoin(seal) | XChain::Liquid(seal) => seal.txid_or(default_txid), + XChain::Other(_) => unreachable!(), } } fn outpoint_or(&self, default_txid: Txid) -> Outpoint { match self { XChain::Bitcoin(seal) | XChain::Liquid(seal) => seal.outpoint_or(default_txid), + XChain::Other(_) => unreachable!(), } } } @@ -135,6 +142,7 @@ impl XChain { match self { XChain::Bitcoin(seal) => seal.method(), XChain::Liquid(seal) => seal.method(), + XChain::Other(_) => unreachable!(), } } @@ -150,6 +158,7 @@ impl XChain { let outpoint = seal.outpoint()?; XChain::Liquid(ExplicitSeal::new(seal.method(), outpoint)) } + XChain::Other(_) => unreachable!(), }) } @@ -228,7 +237,7 @@ impl WitnessOrd { } } -pub type XPubWitness = XChain; +pub type XPubWitness = XChain; pub type XWitness = XChain>; @@ -237,6 +246,7 @@ impl XPubWitness { match self { Self::Bitcoin(tx) => WitnessId::Bitcoin(tx.txid()), Self::Liquid(tx) => WitnessId::Liquid(tx.txid()), + Self::Other(_) => unreachable!(), } } } @@ -246,6 +256,7 @@ impl XWitness { match self { Self::Bitcoin(w) => WitnessId::Bitcoin(w.txid), Self::Liquid(w) => WitnessId::Liquid(w.txid), + Self::Other(_) => unreachable!(), } } } @@ -257,6 +268,7 @@ impl SealWitness for XWitness { fn verify_seal(&self, seal: &Seal, msg: &Self::Message) -> Result<(), Self::Error> { match self { Self::Bitcoin(witness) | Self::Liquid(witness) => witness.verify_seal(seal, msg), + Self::Other(_) => unreachable!(), } } @@ -270,6 +282,7 @@ impl SealWitness for XWitness { { match self { Self::Bitcoin(witness) | Self::Liquid(witness) => witness.verify_many_seals(seals, msg), + Self::Other(_) => unreachable!(), } } } diff --git a/src/contract/xchain.rs b/src/contract/xchain.rs index 7ce86252..431486cb 100644 --- a/src/contract/xchain.rs +++ b/src/contract/xchain.rs @@ -21,6 +21,7 @@ // limitations under the License. use std::cmp::Ordering; +use std::convert::Infallible; use std::fmt::{Debug, Display, Formatter}; use std::str::FromStr; use std::{fmt, io}; @@ -30,7 +31,8 @@ use bp::{Bp, Outpoint}; use commit_verify::{Conceal, StrictHash}; use strict_encoding::{ DecodeError, DefineUnion, ReadTuple, ReadUnion, StrictDecode, StrictDumb, StrictEncode, - StrictSum, StrictType, StrictUnion, TypedRead, TypedWrite, WriteUnion, + StrictEnum, StrictSum, StrictType, StrictUnion, TypedRead, TypedWrite, VariantError, + WriteUnion, }; use crate::{Layer1, OutputSeal, XOutputSeal, LIB_NAME_RGB}; @@ -112,6 +114,56 @@ impl AltLayer1 { } } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase", tag = "chain", content = "data") +)] +pub enum Impossible {} + +impl TryFrom for Impossible { + type Error = VariantError; + + fn try_from(_: u8) -> Result { panic!("must not be instantiated") } +} +impl From for u8 { + fn from(_: Impossible) -> Self { unreachable!() } +} + +impl StrictDumb for Impossible { + fn strict_dumb() -> Self { panic!("must not be instantiated") } +} +impl StrictType for Impossible { + const STRICT_LIB_NAME: &'static str = LIB_NAME_RGB; +} +impl StrictSum for Impossible { + const ALL_VARIANTS: &'static [(u8, &'static str)] = &[]; + fn variant_name(&self) -> &'static str { unreachable!() } +} +impl StrictEnum for Impossible {} +impl StrictEncode for Impossible { + fn strict_encode(&self, _writer: W) -> io::Result { unreachable!() } +} +impl StrictDecode for Impossible { + fn strict_decode(_reader: &mut impl TypedRead) -> Result { + panic!("must not be deserialized") + } +} + +impl Conceal for Impossible { + type Concealed = Self; + fn conceal(&self) -> Self::Concealed { unreachable!() } +} + +impl Display for Impossible { + fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { unreachable!() } +} +impl FromStr for Impossible { + type Err = Infallible; + fn from_str(_: &str) -> Result { panic!("must not be parsed") } +} + #[derive(Wrapper, WrapperMut, Clone, PartialEq, Eq, Hash, Debug, Default, From)] #[wrapper(Deref)] #[wrapper_mut(DerefMut)] @@ -132,39 +184,43 @@ pub struct AltLayer1Set(TinyOrdSet); derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase", tag = "chain", content = "data") )] -pub enum XChain { +pub enum XChain { Bitcoin(T), Liquid(T), + + Other(X), } -impl PartialOrd for XChain { +impl PartialOrd for XChain { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for XChain { +impl Ord for XChain { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { - (Self::Bitcoin(_), Self::Liquid(_)) => Ordering::Greater, - (Self::Liquid(_), Self::Bitcoin(_)) => Ordering::Less, - (Self::Bitcoin(t1) | Self::Liquid(t1), Self::Bitcoin(t2) | Self::Liquid(t2)) => { - t1.cmp(t2) - } + (Self::Bitcoin(t1), Self::Bitcoin(t2)) => t1.cmp(t2), + (Self::Liquid(t1), Self::Liquid(t2)) => t1.cmp(t2), + (Self::Bitcoin(_), _) => Ordering::Greater, + (_, Self::Bitcoin(_)) => Ordering::Less, + (Self::Liquid(_), _) => Ordering::Greater, + (_, Self::Liquid(_)) => Ordering::Less, + (Self::Other(x1), Self::Other(x2)) => x1.cmp(x2), } } } -impl Conceal for XChain { - type Concealed = XChain; +impl Conceal for XChain { + type Concealed = XChain; #[inline] - fn conceal(&self) -> Self::Concealed { self.map_ref(|t| t.conceal()) } + fn conceal(&self) -> Self::Concealed { self.map2_ref(|t| t.conceal(), |x| x.conceal()) } } impl StrictType for XChain where T: StrictDumb + StrictType { - const STRICT_LIB_NAME: &'static str = crate::LIB_NAME_RGB; + const STRICT_LIB_NAME: &'static str = LIB_NAME_RGB; } impl StrictSum for XChain where T: StrictDumb + StrictType @@ -175,6 +231,7 @@ where T: StrictDumb + StrictType match self { XChain::Bitcoin(_) => Self::ALL_VARIANTS[0].1, XChain::Liquid(_) => Self::ALL_VARIANTS[1].1, + XChain::Other(_) => unreachable!(), } } } @@ -196,6 +253,7 @@ where T: StrictDumb + StrictEncode Ok(match self { XChain::Bitcoin(t) => w.write_newtype(vname!(Self::ALL_VARIANTS[0].1), t)?, XChain::Liquid(t) => w.write_newtype(vname!(Self::ALL_VARIANTS[1].1), t)?, + XChain::Other(_) => unreachable!(), } .complete()) }) @@ -215,26 +273,12 @@ where T: StrictDumb + StrictDecode } } -impl XChain { - pub fn with(layer1: Layer1, data: impl Into) -> Self { - match layer1 { - Layer1::Bitcoin => XChain::Bitcoin(data.into()), - Layer1::Liquid => XChain::Liquid(data.into()), - } - } - - pub fn is_bitcoin(&self) -> bool { matches!(self, XChain::Bitcoin(_)) } - pub fn is_liquid(&self) -> bool { matches!(self, XChain::Liquid(_)) } - pub fn is_bp(&self) -> bool { - match self { - XChain::Bitcoin(_) | XChain::Liquid(_) => true, - } - } - +impl XChain { pub fn layer1(&self) -> Layer1 { match self { XChain::Bitcoin(_) => Layer1::Bitcoin, XChain::Liquid(_) => Layer1::Liquid, + XChain::Other(_) => unreachable!(), } } @@ -243,6 +287,7 @@ impl XChain { match self { XChain::Bitcoin(t) => Bp::Bitcoin(t), XChain::Liquid(t) => Bp::Liquid(t), + XChain::Other(_) => unreachable!(), } } @@ -251,12 +296,14 @@ impl XChain { match self { XChain::Bitcoin(t) => Bp::Bitcoin(t), XChain::Liquid(t) => Bp::Liquid(t), + XChain::Other(_) => unreachable!(), } } pub fn as_reduced_unsafe(&self) -> &T { match self { XChain::Bitcoin(t) | XChain::Liquid(t) => t, + XChain::Other(_) => unreachable!(), } } @@ -265,6 +312,7 @@ impl XChain { match self { Self::Bitcoin(t) => XChain::Bitcoin(f(t)), Self::Liquid(t) => XChain::Liquid(f(t)), + Self::Other(_) => unreachable!(), } } @@ -273,6 +321,7 @@ impl XChain { match self { Self::Bitcoin(t) => XChain::Bitcoin(f(t)), Self::Liquid(t) => XChain::Liquid(f(t)), + Self::Other(_) => unreachable!(), } } @@ -282,6 +331,7 @@ impl XChain { match self { Self::Bitcoin(t) => f(t).map(XChain::Bitcoin), Self::Liquid(t) => f(t).map(XChain::Liquid), + Self::Other(_) => unreachable!(), } } @@ -291,6 +341,7 @@ impl XChain { match self { Self::Bitcoin(t) => f(t).map(XChain::Bitcoin), Self::Liquid(t) => f(t).map(XChain::Liquid), + Self::Other(_) => unreachable!(), } } @@ -300,6 +351,7 @@ impl XChain { match self { Self::Bitcoin(t) => f(t).map(XChain::Bitcoin), Self::Liquid(t) => f(t).map(XChain::Liquid), + Self::Other(_) => unreachable!(), } } @@ -309,6 +361,7 @@ impl XChain { match self { Self::Bitcoin(t) => f(t).map(XChain::Bitcoin), Self::Liquid(t) => f(t).map(XChain::Liquid), + Self::Other(_) => unreachable!(), } } @@ -320,34 +373,133 @@ impl XChain { match self { XChain::Bitcoin(t) => Box::new(t.into_iter().map(XChain::Bitcoin)), XChain::Liquid(t) => Box::new(t.into_iter().map(XChain::Liquid)), + Self::Other(_) => unreachable!(), } } } -impl<'a, T: Copy> XChain<&'a T> { - pub fn copied(self) -> XChain { self.map(|t| *t) } +impl XChain { + pub fn with(layer1: Layer1, data: impl Into) -> Self { + match layer1 { + Layer1::Bitcoin => XChain::Bitcoin(data.into()), + Layer1::Liquid => XChain::Liquid(data.into()), + } + } + + pub fn is_bitcoin(&self) -> bool { matches!(self, XChain::Bitcoin(_)) } + pub fn is_liquid(&self) -> bool { matches!(self, XChain::Liquid(_)) } + pub fn is_bp(&self) -> bool { + match self { + XChain::Bitcoin(_) | XChain::Liquid(_) => true, + XChain::Other(_) => false, + } + } + + /// Maps the value from one internal type into another. + pub fn map2(self, f1: impl FnOnce(T) -> U, f2: impl FnOnce(X) -> Y) -> XChain { + match self { + Self::Bitcoin(t) => XChain::Bitcoin(f1(t)), + Self::Liquid(t) => XChain::Liquid(f1(t)), + Self::Other(x) => XChain::Other(f2(x)), + } + } + + /// Maps the value from a reference on internal type into another. + pub fn map2_ref( + &self, + f1: impl FnOnce(&T) -> U, + f2: impl FnOnce(&X) -> Y, + ) -> XChain { + match self { + Self::Bitcoin(t) => XChain::Bitcoin(f1(t)), + Self::Liquid(t) => XChain::Liquid(f1(t)), + Self::Other(x) => XChain::Other(f2(x)), + } + } + + /// Maps the value from one internal type into another, covering cases which + /// may error. + pub fn try_map2( + self, + f1: impl FnOnce(T) -> Result, + f2: impl FnOnce(X) -> Result, + ) -> Result, E> { + match self { + Self::Bitcoin(t) => f1(t).map(XChain::Bitcoin), + Self::Liquid(t) => f1(t).map(XChain::Liquid), + Self::Other(x) => f2(x).map(XChain::Other), + } + } + + /// Maps the value from one internal type into another, covering cases which + /// may error. + pub fn try_map2_ref( + &self, + f1: impl FnOnce(&T) -> Result, + f2: impl FnOnce(&X) -> Result, + ) -> Result, E> { + match self { + Self::Bitcoin(t) => f1(t).map(XChain::Bitcoin), + Self::Liquid(t) => f1(t).map(XChain::Liquid), + Self::Other(x) => f2(x).map(XChain::Other), + } + } + + /// Maps the value from one internal type into another, covering cases which + /// may result in an optional value. + pub fn maybe_map2( + self, + f1: impl FnOnce(T) -> Option, + f2: impl FnOnce(X) -> Option, + ) -> Option> { + match self { + Self::Bitcoin(t) => f1(t).map(XChain::Bitcoin), + Self::Liquid(t) => f1(t).map(XChain::Liquid), + Self::Other(x) => f2(x).map(XChain::Other), + } + } + + /// Maps the value from one internal type into another, covering cases which + /// may result in an optional value. + pub fn maybe_map2_ref( + &self, + f1: impl FnOnce(&T) -> Option, + f2: impl FnOnce(&X) -> Option, + ) -> Option> { + match self { + Self::Bitcoin(t) => f1(t).map(XChain::Bitcoin), + Self::Liquid(t) => f1(t).map(XChain::Liquid), + Self::Other(x) => f2(x).map(XChain::Other), + } + } +} + +impl<'a, T: Copy, X: Copy> XChain<&'a T, &'a X> { + pub fn copied(self) -> XChain { self.map2(|t| *t, |x| *x) } } -impl<'a, T: Clone> XChain<&'a T> { - pub fn cloned(self) -> XChain { self.map(T::clone) } +impl<'a, T: Clone, X: Clone> XChain<&'a T, &'a X> { + pub fn cloned(self) -> XChain { self.map2(T::clone, X::clone) } } -impl XChain> { +impl XChain, Impossible> { pub fn transpose(self) -> Option> { match self { XChain::Bitcoin(inner) => inner.map(XChain::Bitcoin), XChain::Liquid(inner) => inner.map(XChain::Liquid), + XChain::Other(_) => unreachable!(), } } } -impl Iterator for XChain { +impl Iterator for XChain { type Item = XChain<::Item>; fn next(&mut self) -> Option { match self { XChain::Bitcoin(t) => t.next().map(XChain::Bitcoin), XChain::Liquid(t) => t.next().map(XChain::Liquid), + XChain::Other(_) => unreachable!(), } } } @@ -362,10 +514,12 @@ pub enum XChainParseError { Inner(E), } -impl FromStr for XChain +impl FromStr for XChain where T: StrictDumb + StrictEncode + StrictDecode, T::Err: Debug + Display, + X: StrictDumb + StrictEncode + StrictDecode, + X::Err: Debug + Display, { type Err = XChainParseError; @@ -390,13 +544,16 @@ where } } -impl Display for XChain -where T: StrictDumb + StrictEncode + StrictDecode +impl Display for XChain +where + T: StrictDumb + StrictEncode + StrictDecode, + X: StrictDumb + StrictEncode + StrictDecode, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { XChain::Bitcoin(t) => write!(f, "{XCHAIN_BITCOIN_PREFIX}:{t}"), XChain::Liquid(t) => write!(f, "{XCHAIN_LIQUID_PREFIX}:{t}"), + XChain::Other(x) => Display::fmt(x, f), } } }