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