rustywallet_lightning/
channel.rs

1//! Channel point handling.
2//!
3//! This module provides types for working with Lightning channel points,
4//! which identify specific channels by their funding transaction.
5
6use crate::error::LightningError;
7use std::fmt;
8use std::str::FromStr;
9
10/// A channel point identifying a Lightning channel.
11///
12/// A channel point consists of a funding transaction ID and output index.
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct ChannelPoint {
15    /// Funding transaction ID (32 bytes, reversed for display)
16    txid: [u8; 32],
17    /// Output index in the funding transaction
18    output_index: u32,
19}
20
21impl ChannelPoint {
22    /// Create a new channel point.
23    pub fn new(txid: [u8; 32], output_index: u32) -> Self {
24        Self { txid, output_index }
25    }
26
27    /// Create from txid hex string and output index.
28    pub fn from_parts(txid_hex: &str, output_index: u32) -> Result<Self, LightningError> {
29        let bytes = hex::decode(txid_hex)
30            .map_err(|e| LightningError::InvalidChannelPoint(e.to_string()))?;
31
32        if bytes.len() != 32 {
33            return Err(LightningError::InvalidChannelPoint(format!(
34                "Expected 32 bytes for txid, got {}",
35                bytes.len()
36            )));
37        }
38
39        let mut txid = [0u8; 32];
40        txid.copy_from_slice(&bytes);
41
42        Ok(Self { txid, output_index })
43    }
44
45    /// Parse from string format "txid:index".
46    pub fn parse(s: &str) -> Result<Self, LightningError> {
47        s.parse()
48    }
49
50    /// Get the funding transaction ID.
51    pub fn txid(&self) -> &[u8; 32] {
52        &self.txid
53    }
54
55    /// Get the funding transaction ID as hex (reversed for display).
56    pub fn txid_hex(&self) -> String {
57        // Bitcoin txids are displayed in reversed byte order
58        let mut reversed = self.txid;
59        reversed.reverse();
60        hex::encode(reversed)
61    }
62
63    /// Get the output index.
64    pub fn output_index(&self) -> u32 {
65        self.output_index
66    }
67
68    /// Convert to the standard string format "txid:index".
69    pub fn to_string_format(&self) -> String {
70        format!("{}:{}", self.txid_hex(), self.output_index)
71    }
72
73    /// Get the raw txid bytes (not reversed).
74    pub fn txid_bytes(&self) -> &[u8; 32] {
75        &self.txid
76    }
77}
78
79impl fmt::Display for ChannelPoint {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        write!(f, "{}", self.to_string_format())
82    }
83}
84
85impl FromStr for ChannelPoint {
86    type Err = LightningError;
87
88    fn from_str(s: &str) -> Result<Self, Self::Err> {
89        let parts: Vec<&str> = s.split(':').collect();
90        if parts.len() != 2 {
91            return Err(LightningError::InvalidChannelPoint(
92                "Expected format 'txid:index'".into(),
93            ));
94        }
95
96        let txid_hex = parts[0];
97        let output_index: u32 = parts[1]
98            .parse()
99            .map_err(|e| LightningError::InvalidChannelPoint(format!("Invalid index: {}", e)))?;
100
101        // Parse txid (displayed in reversed order)
102        let bytes = hex::decode(txid_hex)
103            .map_err(|e| LightningError::InvalidChannelPoint(e.to_string()))?;
104
105        if bytes.len() != 32 {
106            return Err(LightningError::InvalidChannelPoint(format!(
107                "Expected 32 bytes for txid, got {}",
108                bytes.len()
109            )));
110        }
111
112        let mut txid = [0u8; 32];
113        txid.copy_from_slice(&bytes);
114        // Reverse to internal format
115        txid.reverse();
116
117        Ok(Self { txid, output_index })
118    }
119}
120
121/// Short channel ID (SCID) - compact channel identifier.
122///
123/// Encodes block height, transaction index, and output index.
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
125pub struct ShortChannelId(u64);
126
127impl ShortChannelId {
128    /// Create from components.
129    pub fn new(block_height: u32, tx_index: u32, output_index: u16) -> Self {
130        let scid = ((block_height as u64) << 40)
131            | ((tx_index as u64) << 16)
132            | (output_index as u64);
133        Self(scid)
134    }
135
136    /// Create from raw u64 value.
137    pub fn from_u64(value: u64) -> Self {
138        Self(value)
139    }
140
141    /// Parse from string format "block:tx:output".
142    pub fn parse(s: &str) -> Result<Self, LightningError> {
143        let parts: Vec<&str> = s.split('x').collect();
144        if parts.len() != 3 {
145            return Err(LightningError::InvalidChannelPoint(
146                "Expected format 'blockxTxxoutput'".into(),
147            ));
148        }
149
150        let block: u32 = parts[0]
151            .parse()
152            .map_err(|e| LightningError::InvalidChannelPoint(format!("Invalid block: {}", e)))?;
153        let tx: u32 = parts[1]
154            .parse()
155            .map_err(|e| LightningError::InvalidChannelPoint(format!("Invalid tx: {}", e)))?;
156        let output: u16 = parts[2]
157            .parse()
158            .map_err(|e| LightningError::InvalidChannelPoint(format!("Invalid output: {}", e)))?;
159
160        Ok(Self::new(block, tx, output))
161    }
162
163    /// Get the raw u64 value.
164    pub fn as_u64(&self) -> u64 {
165        self.0
166    }
167
168    /// Get the block height.
169    pub fn block_height(&self) -> u32 {
170        (self.0 >> 40) as u32
171    }
172
173    /// Get the transaction index.
174    pub fn tx_index(&self) -> u32 {
175        ((self.0 >> 16) & 0xFFFFFF) as u32
176    }
177
178    /// Get the output index.
179    pub fn output_index(&self) -> u16 {
180        (self.0 & 0xFFFF) as u16
181    }
182}
183
184impl fmt::Display for ShortChannelId {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(
187            f,
188            "{}x{}x{}",
189            self.block_height(),
190            self.tx_index(),
191            self.output_index()
192        )
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_channel_point_parse() {
202        let cp_str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef:1";
203        let cp = ChannelPoint::parse(cp_str).unwrap();
204
205        assert_eq!(cp.output_index(), 1);
206        assert_eq!(cp.to_string(), cp_str);
207    }
208
209    #[test]
210    fn test_channel_point_from_parts() {
211        let txid = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
212        let cp = ChannelPoint::from_parts(txid, 0).unwrap();
213
214        assert_eq!(cp.output_index(), 0);
215    }
216
217    #[test]
218    fn test_short_channel_id() {
219        let scid = ShortChannelId::new(700000, 1234, 0);
220
221        assert_eq!(scid.block_height(), 700000);
222        assert_eq!(scid.tx_index(), 1234);
223        assert_eq!(scid.output_index(), 0);
224    }
225
226    #[test]
227    fn test_short_channel_id_display() {
228        let scid = ShortChannelId::new(700000, 1234, 1);
229        assert_eq!(scid.to_string(), "700000x1234x1");
230    }
231
232    #[test]
233    fn test_short_channel_id_parse() {
234        let scid = ShortChannelId::parse("700000x1234x1").unwrap();
235
236        assert_eq!(scid.block_height(), 700000);
237        assert_eq!(scid.tx_index(), 1234);
238        assert_eq!(scid.output_index(), 1);
239    }
240}