Skip to main content

lexe_common/api/
models.rs

1use anyhow::Context;
2use bitcoin::{consensus::Decodable, io::Cursor};
3use lexe_serde::hexstr_or_bytes;
4use serde::{Deserialize, Serialize};
5
6#[cfg(any(test, feature = "test-utils"))]
7use crate::test_utils::arbitrary;
8use crate::{
9    ln::{amount::Amount, hashes::Txid, network::Network},
10    time::TimestampMs,
11};
12
13/// A response to a status check.
14#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
15pub struct Status {
16    /// The current time, according to this service.
17    pub timestamp: TimestampMs,
18    // TODO(max): We can add more metrics here, like CPU and memory usage (if
19    // available within SGX), # of tasks, etc.
20}
21
22/// A request to sign a message using the node ID secret key.
23#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
24pub struct SignMsgRequest {
25    /// The message to be signed. (Will be signed as UTF-8 bytes.)
26    pub msg: String,
27}
28
29#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
30pub struct SignMsgResponse {
31    /// The `zbase32`-encoded signature corresponding to the message.
32    pub sig: String,
33}
34
35/// The user node or LSP broadcasted an on-chain transaction.
36#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
37pub struct BroadcastedTx {
38    /// (PK)
39    pub txid: Txid,
40    /// Consensus-encoded [`bitcoin::Transaction`].
41    #[serde(with = "hexstr_or_bytes")]
42    pub tx: Vec<u8>,
43
44    /// When this tx was broadcasted.
45    pub created_at: TimestampMs,
46}
47
48#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
49pub struct BroadcastedTxInfo {
50    /// (PK)
51    pub txid: Txid,
52    /// Consensus-encoded [`bitcoin::Transaction`].
53    #[serde(with = "hexstr_or_bytes")]
54    pub tx: Vec<u8>,
55    /// When this tx was broadcasted.
56    pub created_at: TimestampMs,
57    /// Total amount from transaction.
58    pub total_outputs: Amount,
59    /// Destination addresses of the transaction.
60    pub output_destinations: Vec<String>,
61    /// Previous outpoints of the transaction.
62    pub inputs: Vec<String>,
63    /// Confirmation block height of transaction.
64    pub confirmation_block_height: Option<u32>,
65}
66
67impl BroadcastedTxInfo {
68    pub fn from_broadcasted_tx(
69        broadcasted_tx: BroadcastedTx,
70        network: Network,
71        confirmation_block_height: Option<u32>,
72    ) -> anyhow::Result<Self> {
73        let mut reader = Cursor::new(&broadcasted_tx.tx);
74
75        let tx = bitcoin::Transaction::consensus_decode(&mut reader)
76            .context("Could not parse consensus-encoded transaction")?;
77
78        let total_outputs =
79            tx.output.iter().map(|o| o.value.to_sat()).sum::<u64>();
80        let total_outputs = Amount::try_from_sats_u64(total_outputs)
81            .context("Output amount conversion error")?;
82
83        let output_destinations = tx
84            .output
85            .iter()
86            .map(|o| {
87                bitcoin::Address::from_script(
88                    &o.script_pubkey,
89                    network.to_bitcoin(),
90                )
91                .map_or(o.script_pubkey.to_string(), |addr| addr.to_string())
92            })
93            .collect::<Vec<_>>();
94
95        let inputs = tx
96            .input
97            .iter()
98            .map(|i| i.previous_output.to_string())
99            .collect::<Vec<_>>();
100
101        let tx_info = BroadcastedTxInfo {
102            total_outputs,
103            created_at: broadcasted_tx.created_at,
104            output_destinations,
105            inputs,
106            txid: broadcasted_tx.txid,
107            tx: broadcasted_tx.tx,
108            confirmation_block_height,
109        };
110        Ok(tx_info)
111    }
112}
113
114#[cfg(any(test, feature = "test-utils"))]
115mod arbitrary_impl {
116    use proptest::{
117        arbitrary::{Arbitrary, any},
118        collection::vec,
119        option,
120        strategy::{BoxedStrategy, Strategy},
121    };
122
123    use super::*;
124
125    impl Arbitrary for BroadcastedTx {
126        type Parameters = ();
127        type Strategy = BoxedStrategy<Self>;
128
129        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
130            (
131                arbitrary::any_raw_tx_bytes(),
132                any::<Txid>(),
133                any::<TimestampMs>(),
134            )
135                .prop_map(|(tx, txid, created_at)| Self {
136                    tx,
137                    txid,
138                    created_at,
139                })
140                .boxed()
141        }
142    }
143
144    impl Arbitrary for BroadcastedTxInfo {
145        type Parameters = ();
146        type Strategy = BoxedStrategy<Self>;
147
148        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
149            let any_vec_input_str = vec(
150                (arbitrary::any_outpoint())
151                    .prop_map(|outpoint| outpoint.to_string()),
152                0..=8,
153            );
154            let any_vec_output_destination_str = vec(
155                (arbitrary::any_mainnet_addr())
156                    .prop_map(|address| address.to_string()),
157                0..=8,
158            );
159            let any_confirmation_block_height_optional =
160                option::of(any::<u32>());
161
162            (
163                any::<BroadcastedTx>(),
164                any::<Amount>(),
165                any_vec_output_destination_str,
166                any_vec_input_str,
167                any_confirmation_block_height_optional,
168            )
169                .prop_map(
170                    |(
171                        tx,
172                        total_outputs,
173                        output_destinations,
174                        inputs,
175                        confirmation_block_height,
176                    )| Self {
177                        txid: tx.txid,
178                        tx: tx.tx,
179                        created_at: tx.created_at,
180                        total_outputs,
181                        output_destinations,
182                        inputs,
183                        confirmation_block_height,
184                    },
185                )
186                .boxed()
187        }
188    }
189}
190
191#[cfg(test)]
192mod test {
193    use super::*;
194    use crate::test_utils::roundtrip;
195
196    #[test]
197    fn broadcasted_tx_roundtrip_proptest() {
198        roundtrip::json_value_roundtrip_proptest::<BroadcastedTx>();
199    }
200
201    #[test]
202    fn broadcasted_tx_info_roundtrip_proptest() {
203        roundtrip::json_value_roundtrip_proptest::<BroadcastedTxInfo>();
204    }
205}