titan_types/
outpoint.rs

1use std::str::FromStr;
2
3use bitcoin::{hashes::Hash, OutPoint, Txid};
4use borsh::{BorshDeserialize, BorshSerialize};
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7use crate::SerializedTxid;
8
9#[derive(PartialEq, Eq, Hash, Clone, Copy, BorshSerialize, BorshDeserialize)]
10pub struct SerializedOutPoint([u8; 36]);
11
12impl SerializedOutPoint {
13    pub fn new(txid: &[u8], vout: u32) -> Self {
14        let mut outpoint = [0u8; 36];
15        outpoint[..32].copy_from_slice(txid);
16        outpoint[32..].copy_from_slice(&vout.to_le_bytes());
17        Self(outpoint)
18    }
19
20    pub fn from_txid_vout(txid: &SerializedTxid, vout: u32) -> Self {
21        Self::new(txid.as_ref(), vout)
22    }
23
24    pub fn txid(&self) -> &[u8] {
25        &self.0[..32]
26    }
27
28    pub fn to_txid(&self) -> Txid {
29        Txid::from_raw_hash(Hash::from_slice(self.txid()).unwrap())
30    }
31
32    pub fn to_serialized_txid(&self) -> SerializedTxid {
33        SerializedTxid::from(self.txid())
34    }
35
36    pub fn vout(&self) -> u32 {
37        u32::from_le_bytes(self.0[32..].try_into().unwrap())
38    }
39}
40
41impl AsRef<[u8]> for SerializedOutPoint {
42    fn as_ref(&self) -> &[u8] {
43        &self.0
44    }
45}
46
47impl From<SerializedOutPoint> for OutPoint {
48    fn from(outpoint: SerializedOutPoint) -> Self {
49        OutPoint::new(outpoint.to_txid(), outpoint.vout())
50    }
51}
52
53impl From<OutPoint> for SerializedOutPoint {
54    fn from(outpoint: OutPoint) -> Self {
55        Self::new(outpoint.txid.as_raw_hash().as_byte_array(), outpoint.vout)
56    }
57}
58
59impl From<[u8; 36]> for SerializedOutPoint {
60    fn from(outpoint: [u8; 36]) -> Self {
61        Self(outpoint)
62    }
63}
64
65impl From<&[u8]> for SerializedOutPoint {
66    fn from(outpoint: &[u8]) -> Self {
67        Self(outpoint.try_into().unwrap())
68    }
69}
70
71impl TryFrom<Box<[u8]>> for SerializedOutPoint {
72    type Error = std::array::TryFromSliceError;
73
74    fn try_from(outpoint: Box<[u8]>) -> Result<Self, Self::Error> {
75        let array: [u8; 36] = outpoint.as_ref().try_into()?;
76        Ok(Self(array))
77    }
78}
79
80impl std::fmt::Debug for SerializedOutPoint {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        write!(f, "{}:{}", self.to_txid(), self.vout())
83    }
84}
85
86impl std::fmt::Display for SerializedOutPoint {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        write!(f, "{}:{}", self.to_txid(), self.vout())
89    }
90}
91
92impl FromStr for SerializedOutPoint {
93    type Err = std::io::Error;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        let parts: Vec<&str> = s.split(':').collect();
97        Ok(Self::from_txid_vout(
98            &SerializedTxid::from_str(parts[0]).map_err(|_| {
99                std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid txid")
100            })?,
101            parts[1].parse().map_err(|_| {
102                std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid vout")
103            })?,
104        ))
105    }
106}
107
108impl Serialize for SerializedOutPoint {
109    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110    where
111        S: Serializer,
112    {
113        // Serialize as a struct with txid and vout fields like OutPoint
114        use serde::ser::SerializeStruct;
115        let mut state = serializer.serialize_struct("SerializedOutPoint", 2)?;
116        state.serialize_field("txid", &self.to_txid())?;
117        state.serialize_field("vout", &self.vout())?;
118        state.end()
119    }
120}
121
122impl<'de> Deserialize<'de> for SerializedOutPoint {
123    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124    where
125        D: Deserializer<'de>,
126    {
127        use serde::de::{self, MapAccess, Visitor};
128        use std::fmt;
129
130        #[derive(Deserialize)]
131        #[serde(field_identifier, rename_all = "lowercase")]
132        enum Field {
133            Txid,
134            Vout,
135        }
136
137        struct SerializedOutPointVisitor;
138
139        impl<'de> Visitor<'de> for SerializedOutPointVisitor {
140            type Value = SerializedOutPoint;
141
142            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
143                formatter.write_str("struct SerializedOutPoint")
144            }
145
146            fn visit_map<V>(self, mut map: V) -> Result<SerializedOutPoint, V::Error>
147            where
148                V: MapAccess<'de>,
149            {
150                let mut txid = None;
151                let mut vout = None;
152                while let Some(key) = map.next_key()? {
153                    match key {
154                        Field::Txid => {
155                            if txid.is_some() {
156                                return Err(de::Error::duplicate_field("txid"));
157                            }
158                            txid = Some(map.next_value()?);
159                        }
160                        Field::Vout => {
161                            if vout.is_some() {
162                                return Err(de::Error::duplicate_field("vout"));
163                            }
164                            vout = Some(map.next_value()?);
165                        }
166                    }
167                }
168                let txid: SerializedTxid = txid.ok_or_else(|| de::Error::missing_field("txid"))?;
169                let vout: u32 = vout.ok_or_else(|| de::Error::missing_field("vout"))?;
170                Ok(SerializedOutPoint::from_txid_vout(&txid, vout))
171            }
172        }
173
174        const FIELDS: &'static [&'static str] = &["txid", "vout"];
175        deserializer.deserialize_struct("SerializedOutPoint", FIELDS, SerializedOutPointVisitor)
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use bitcoin::{hashes::Hash, OutPoint, Txid};
183    use borsh::{BorshDeserialize, BorshSerialize};
184
185    /// Helper function to test borsh serialization roundtrip
186    fn test_borsh_roundtrip<T>(original: &T) -> T
187    where
188        T: BorshSerialize + BorshDeserialize + std::fmt::Debug + PartialEq,
189    {
190        let serialized = borsh::to_vec(original).expect("Failed to serialize");
191        let deserialized = borsh::from_slice(&serialized).expect("Failed to deserialize");
192        assert_eq!(original, &deserialized, "Borsh roundtrip failed");
193        deserialized
194    }
195
196    /// Helper function to test serde serialization roundtrip
197    fn test_serde_roundtrip<T>(original: &T) -> T
198    where
199        T: serde::Serialize + for<'de> serde::Deserialize<'de> + std::fmt::Debug + PartialEq,
200    {
201        let serialized = serde_json::to_string(original).expect("Failed to serialize");
202        let deserialized = serde_json::from_str(&serialized).expect("Failed to deserialize");
203        assert_eq!(original, &deserialized, "Serde roundtrip failed");
204        deserialized
205    }
206
207    fn create_test_outpoint() -> SerializedOutPoint {
208        SerializedOutPoint::new(&[42u8; 32], 12345)
209    }
210
211    fn create_zero_outpoint() -> SerializedOutPoint {
212        SerializedOutPoint::new(&[0u8; 32], 0)
213    }
214
215    fn create_max_outpoint() -> SerializedOutPoint {
216        SerializedOutPoint::new(&[255u8; 32], u32::MAX)
217    }
218
219    #[test]
220    fn test_serialized_outpoint_creation() {
221        let txid_bytes = [42u8; 32];
222        let vout = 12345;
223        let outpoint = SerializedOutPoint::new(&txid_bytes, vout);
224
225        assert_eq!(outpoint.txid(), &txid_bytes);
226        assert_eq!(outpoint.vout(), vout);
227    }
228
229    #[test]
230    fn test_serialized_outpoint_from_txid_vout() {
231        let txid = SerializedTxid::from([42u8; 32]);
232        let vout = 12345;
233        let outpoint = SerializedOutPoint::from_txid_vout(&txid, vout);
234
235        assert_eq!(outpoint.txid(), txid.as_bytes());
236        assert_eq!(outpoint.vout(), vout);
237    }
238
239    #[test]
240    fn test_serialized_outpoint_borsh_roundtrip() {
241        let original = create_test_outpoint();
242        test_borsh_roundtrip(&original);
243    }
244
245    #[test]
246    fn test_serialized_outpoint_serde_roundtrip() {
247        let original = create_test_outpoint();
248        test_serde_roundtrip(&original);
249    }
250
251    #[test]
252    fn test_serialized_outpoint_zero_values() {
253        let original = create_zero_outpoint();
254        test_borsh_roundtrip(&original);
255        test_serde_roundtrip(&original);
256    }
257
258    #[test]
259    fn test_serialized_outpoint_max_values() {
260        let original = create_max_outpoint();
261        test_borsh_roundtrip(&original);
262        test_serde_roundtrip(&original);
263    }
264
265    #[test]
266    fn test_serialized_outpoint_conversions() {
267        let original_bytes = [42u8; 32];
268        let vout = 12345;
269        let serialized_outpoint = SerializedOutPoint::new(&original_bytes, vout);
270
271        // Test conversion to Bitcoin OutPoint
272        let bitcoin_outpoint: OutPoint = serialized_outpoint.into();
273        assert_eq!(bitcoin_outpoint.vout, vout);
274
275        // Test conversion back from Bitcoin OutPoint
276        let converted_back: SerializedOutPoint = bitcoin_outpoint.into();
277        assert_eq!(converted_back.vout(), vout);
278        assert_eq!(converted_back.txid(), &original_bytes);
279    }
280
281    #[test]
282    fn test_serialized_outpoint_display() {
283        let txid_bytes = [1u8; 32];
284        let vout = 42;
285        let outpoint = SerializedOutPoint::new(&txid_bytes, vout);
286
287        let display_str = outpoint.to_string();
288        assert!(display_str.contains(":"));
289        assert!(display_str.contains(&vout.to_string()));
290    }
291
292    #[test]
293    fn test_serialized_outpoint_from_str() {
294        let txid_str = "1111111111111111111111111111111111111111111111111111111111111111";
295        let vout = 42;
296        let outpoint_str = format!("{}:{}", txid_str, vout);
297
298        let outpoint = SerializedOutPoint::from_str(&outpoint_str)
299            .expect("Should parse valid outpoint string");
300
301        assert_eq!(outpoint.vout(), vout);
302    }
303
304    #[test]
305    fn test_serialized_outpoint_from_array() {
306        let mut array = [0u8; 36];
307        array[..32].copy_from_slice(&[42u8; 32]);
308        array[32..].copy_from_slice(&12345u32.to_le_bytes());
309
310        let outpoint = SerializedOutPoint::from(array);
311        assert_eq!(outpoint.txid(), &[42u8; 32]);
312        assert_eq!(outpoint.vout(), 12345);
313    }
314
315    #[test]
316    fn test_serialized_outpoint_from_slice() {
317        let slice = &[42u8; 36];
318        let outpoint = SerializedOutPoint::from(slice.as_slice());
319        assert_eq!(outpoint.txid(), &[42u8; 32]);
320        assert_eq!(outpoint.vout(), u32::from_le_bytes([42u8; 4]));
321    }
322
323    #[test]
324    fn test_serialized_outpoint_try_from_box() {
325        let boxed_slice: Box<[u8]> = Box::new([42u8; 36]);
326        let outpoint =
327            SerializedOutPoint::try_from(boxed_slice).expect("Should convert from Box<[u8]>");
328        assert_eq!(outpoint.txid(), &[42u8; 32]);
329    }
330
331    #[test]
332    fn test_serialized_outpoint_as_ref() {
333        let outpoint = create_test_outpoint();
334        let as_bytes: &[u8] = outpoint.as_ref();
335        assert_eq!(as_bytes.len(), 36);
336    }
337
338    #[test]
339    fn test_serialized_outpoint_to_txid() {
340        let txid_bytes = [42u8; 32];
341        let outpoint = SerializedOutPoint::new(&txid_bytes, 0);
342        let bitcoin_txid = outpoint.to_txid();
343
344        // Verify the txid conversion works correctly
345        assert_eq!(bitcoin_txid.as_raw_hash().as_byte_array(), &txid_bytes);
346    }
347
348    #[test]
349    fn test_serialized_outpoint_to_serialized_txid() {
350        let txid_bytes = [42u8; 32];
351        let outpoint = SerializedOutPoint::new(&txid_bytes, 0);
352        let serialized_txid = outpoint.to_serialized_txid();
353
354        assert_eq!(serialized_txid.as_bytes(), &txid_bytes);
355    }
356
357    #[test]
358    fn test_serialized_outpoint_consistency() {
359        let original = create_test_outpoint();
360
361        // Test that multiple serializations produce the same result
362        let serialized1 = borsh::to_vec(&original).unwrap();
363        let serialized2 = borsh::to_vec(&original).unwrap();
364        assert_eq!(serialized1, serialized2);
365
366        // Test that deserialization produces the same value
367        let deserialized1 = borsh::from_slice::<SerializedOutPoint>(&serialized1).unwrap();
368        let deserialized2 = borsh::from_slice::<SerializedOutPoint>(&serialized2).unwrap();
369        assert_eq!(deserialized1, deserialized2);
370        assert_eq!(original, deserialized1);
371    }
372
373    #[test]
374    fn test_serialized_outpoint_hash_and_eq() {
375        let outpoint1 = create_test_outpoint();
376        let outpoint2 = create_test_outpoint();
377        let outpoint3 = create_zero_outpoint();
378
379        assert_eq!(outpoint1, outpoint2);
380        assert_ne!(outpoint1, outpoint3);
381
382        // Test that equal values have the same hash
383        use std::collections::hash_map::DefaultHasher;
384        use std::hash::{Hash, Hasher};
385
386        let mut hasher1 = DefaultHasher::new();
387        let mut hasher2 = DefaultHasher::new();
388        outpoint1.hash(&mut hasher1);
389        outpoint2.hash(&mut hasher2);
390        assert_eq!(hasher1.finish(), hasher2.finish());
391    }
392
393    #[test]
394    fn test_serialized_outpoint_edge_cases() {
395        // Test with different txid patterns
396        let patterns = [[0u8; 32], [255u8; 32], {
397            let mut pattern = [0u8; 32];
398            pattern[0] = 255;
399            pattern[31] = 255;
400            pattern
401        }];
402
403        for pattern in patterns {
404            let outpoint = SerializedOutPoint::new(&pattern, 42);
405            test_borsh_roundtrip(&outpoint);
406            test_serde_roundtrip(&outpoint);
407        }
408    }
409}