ree_types/
txid.rs

1use alloc::{borrow::Cow, str::FromStr};
2use candid::{
3    CandidType,
4    types::{Serializer, Type, TypeInner},
5};
6use ic_stable_structures::storable::{Bound, Storable};
7
8/// The Bitcoin Txid compatible with the IC storage.
9#[derive(Eq, Ord, PartialOrd, PartialEq, Clone, Copy, Debug)]
10pub struct Txid([u8; 32]);
11
12impl CandidType for Txid {
13    fn _ty() -> Type {
14        TypeInner::Text.into()
15    }
16
17    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
18    where
19        S: Serializer,
20    {
21        let rev = self.0.iter().rev().copied().collect::<Vec<_>>();
22        serializer.serialize_text(&hex::encode(&rev))
23    }
24}
25
26impl FromStr for Txid {
27    type Err = String;
28
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        let bytes = bitcoin::Txid::from_str(s).map_err(|_| "Invalid txid".to_string())?;
31        Ok(Self(*AsRef::<[u8; 32]>::as_ref(&bytes)))
32    }
33}
34
35impl Storable for Txid {
36    fn to_bytes(&self) -> Cow<'_, [u8]> {
37        let bytes = bincode::serialize(self).unwrap();
38        Cow::Owned(bytes)
39    }
40
41    fn into_bytes(self) -> Vec<u8> {
42        bincode::serialize(&self).unwrap()
43    }
44
45    fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
46        bincode::deserialize(bytes.as_ref()).unwrap()
47    }
48
49    const BOUND: Bound = Bound::Bounded {
50        max_size: 72,
51        is_fixed_size: true,
52    };
53}
54
55impl Into<bitcoin::Txid> for Txid {
56    fn into(self) -> bitcoin::Txid {
57        use bitcoin::hashes::Hash;
58        bitcoin::Txid::from_byte_array(self.0)
59    }
60}
61
62impl From<bitcoin::Txid> for Txid {
63    fn from(txid: bitcoin::Txid) -> Self {
64        Self(*AsRef::<[u8; 32]>::as_ref(&txid))
65    }
66}
67
68impl AsRef<[u8; 32]> for Txid {
69    fn as_ref(&self) -> &[u8; 32] {
70        &self.0
71    }
72}
73
74impl AsRef<[u8]> for Txid {
75    fn as_ref(&self) -> &[u8] {
76        &self.0
77    }
78}
79
80impl core::fmt::Display for Txid {
81    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
82        let rev = self.0.iter().rev().copied().collect::<Vec<_>>();
83        write!(f, "{}", hex::encode(&rev))
84    }
85}
86
87struct TxidVisitor;
88
89impl<'de> serde::de::Visitor<'de> for TxidVisitor {
90    type Value = Txid;
91
92    fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
93        write!(formatter, "a Bitcoin Txid")
94    }
95
96    fn visit_str<E>(self, value: &str) -> Result<Txid, E>
97    where
98        E: serde::de::Error,
99    {
100        Txid::from_str(value)
101            .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self))
102    }
103}
104
105impl<'de> serde::Deserialize<'de> for Txid {
106    fn deserialize<D>(deserializer: D) -> Result<Txid, D::Error>
107    where
108        D: serde::de::Deserializer<'de>,
109    {
110        deserializer.deserialize_str(TxidVisitor)
111    }
112}
113
114impl serde::Serialize for Txid {
115    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
116    where
117        S: serde::ser::Serializer,
118    {
119        serializer.serialize_str(&self.to_string())
120    }
121}
122
123impl Txid {
124    pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
125        Ok(Txid(bytes.try_into().map_err(|e| {
126            format!("Invalid bytes for a Bitcoin Txid: {:?}", e)
127        })?))
128    }
129
130    pub const fn zero() -> Self {
131        Txid([0; 32])
132    }
133}
134
135impl Default for Txid {
136    fn default() -> Self {
137        Txid::zero()
138    }
139}
140
141#[doc(hidden)]
142#[derive(
143    Debug, Clone, Default, CandidType, serde::Serialize, serde::Deserialize, Eq, PartialEq,
144)]
145pub struct TxRecord {
146    pub txid: Txid,
147    pub pools: Vec<String>,
148}
149
150impl Storable for TxRecord {
151    fn to_bytes(&self) -> Cow<'_, [u8]> {
152        let bytes = bincode::serialize(self).unwrap();
153        Cow::Owned(bytes)
154    }
155
156    fn into_bytes(self) -> Vec<u8> {
157        bincode::serialize(&self).unwrap()
158    }
159
160    fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
161        bincode::deserialize(bytes.as_ref()).unwrap()
162    }
163
164    const BOUND: Bound = Bound::Unbounded;
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_bincode_serde() {
173        let txid_hex = "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2a";
174        let txid_bytes = hex::decode(txid_hex).unwrap();
175        let txid = Txid(txid_bytes.try_into().unwrap());
176        let encoded_hex = hex::encode(bincode::serialize(&txid).unwrap());
177        assert_eq!(
178            txid.0,
179            bincode::deserialize::<Txid>(&hex::decode(encoded_hex).unwrap())
180                .unwrap()
181                .0
182        );
183    }
184
185    #[test]
186    fn test_bytes_serde() {
187        let txid_hex = "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2a";
188        let txid_bytes = hex::decode(txid_hex).unwrap();
189        let txid = Txid::from_bytes(&txid_bytes).unwrap();
190        assert_eq!(txid.0, Txid(txid_bytes.try_into().unwrap()).0);
191    }
192}