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
66#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
67pub struct BroadcastedTxInfo {
68    /// (PK)
69    pub txid: Txid,
70    /// Consensus-encoded [`bitcoin::Transaction`].
71    #[serde(with = "hexstr_or_bytes")]
72    pub tx: Vec<u8>,
73    /// When this tx was broadcasted.
74    pub created_at: TimestampMs,
75    /// Total amount from transaction.
76    pub total_outputs: Amount,
77    /// Destination addresses of the transaction.
78    pub output_destinations: Vec<String>,
79    /// Previous outpoints of the transaction.
80    pub inputs: Vec<String>,
81    /// Confirmation block height of transaction.
82    pub confirmation_block_height: Option<u32>,
83}
84
85impl BroadcastedTxInfo {
86    pub fn from_broadcasted_tx(
87        broadcasted_tx: BroadcastedTx,
88        network: Network,
89        confirmation_block_height: Option<u32>,
90    ) -> anyhow::Result<Self> {
91        let mut reader = Cursor::new(&broadcasted_tx.tx);
92
93        let tx = bitcoin::Transaction::consensus_decode(&mut reader)
94            .context("Could not parse consensus-encoded transaction")?;
95
96        let total_outputs =
97            tx.output.iter().map(|o| o.value.to_sat()).sum::<u64>();
98        let total_outputs = Amount::try_from_sats_u64(total_outputs)
99            .context("Output amount conversion error")?;
100
101        let output_destinations = tx
102            .output
103            .iter()
104            .map(|o| {
105                bitcoin::Address::from_script(
106                    &o.script_pubkey,
107                    network.to_bitcoin(),
108                )
109                .map_or(o.script_pubkey.to_string(), |addr| addr.to_string())
110            })
111            .collect::<Vec<_>>();
112
113        let inputs = tx
114            .input
115            .iter()
116            .map(|i| i.previous_output.to_string())
117            .collect::<Vec<_>>();
118
119        let tx_info = BroadcastedTxInfo {
120            total_outputs,
121            created_at: broadcasted_tx.created_at,
122            output_destinations,
123            inputs,
124            txid: broadcasted_tx.txid,
125            tx: broadcasted_tx.tx,
126            confirmation_block_height,
127        };
128        Ok(tx_info)
129    }
130}
131
132#[cfg(any(test, feature = "test-utils"))]
133mod arbitrary_impl {
134    use proptest::{
135        arbitrary::{Arbitrary, any},
136        collection::vec,
137        option,
138        strategy::{BoxedStrategy, Strategy},
139    };
140
141    use super::*;
142
143    impl Arbitrary for BroadcastedTx {
144        type Parameters = ();
145        type Strategy = BoxedStrategy<Self>;
146
147        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
148            (
149                arbitrary::any_raw_tx_bytes(),
150                any::<Txid>(),
151                any::<TimestampMs>(),
152            )
153                .prop_map(|(tx, txid, created_at)| Self {
154                    tx,
155                    txid,
156                    created_at,
157                })
158                .boxed()
159        }
160    }
161
162    impl Arbitrary for BroadcastedTxInfo {
163        type Parameters = ();
164        type Strategy = BoxedStrategy<Self>;
165
166        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
167            let any_vec_input_str = vec(
168                (arbitrary::any_outpoint())
169                    .prop_map(|outpoint| outpoint.to_string()),
170                0..=8,
171            );
172            let any_vec_output_destination_str = vec(
173                (arbitrary::any_mainnet_addr())
174                    .prop_map(|address| address.to_string()),
175                0..=8,
176            );
177            let any_confirmation_block_height_optional =
178                option::of(any::<u32>());
179
180            (
181                any::<BroadcastedTx>(),
182                any::<Amount>(),
183                any_vec_output_destination_str,
184                any_vec_input_str,
185                any_confirmation_block_height_optional,
186            )
187                .prop_map(
188                    |(
189                        tx,
190                        total_outputs,
191                        output_destinations,
192                        inputs,
193                        confirmation_block_height,
194                    )| Self {
195                        txid: tx.txid,
196                        tx: tx.tx,
197                        created_at: tx.created_at,
198                        total_outputs,
199                        output_destinations,
200                        inputs,
201                        confirmation_block_height,
202                    },
203                )
204                .boxed()
205        }
206    }
207}
208
209#[cfg(test)]
210mod test {
211    use super::*;
212    use crate::test_utils::roundtrip;
213
214    #[test]
215    fn broadcasted_tx_roundtrip_proptest() {
216        roundtrip::json_value_roundtrip_proptest::<BroadcastedTx>();
217    }
218
219    #[test]
220    fn broadcasted_tx_info_roundtrip_proptest() {
221        roundtrip::json_value_roundtrip_proptest::<BroadcastedTxInfo>();
222    }
223}