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 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 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 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 let bitcoin_outpoint: OutPoint = serialized_outpoint.into();
273 assert_eq!(bitcoin_outpoint.vout, vout);
274
275 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 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 let serialized1 = borsh::to_vec(&original).unwrap();
363 let serialized2 = borsh::to_vec(&original).unwrap();
364 assert_eq!(serialized1, serialized2);
365
366 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 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 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}