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#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
16pub struct Status {
17 pub timestamp: TimestampMs,
19 }
22
23#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
25pub struct SignMsgRequest {
26 pub msg: String,
28}
29
30#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
31pub struct SignMsgResponse {
32 pub sig: String,
34}
35
36#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
38pub struct VerifyMsgRequest {
39 pub msg: String,
41 pub sig: String,
43 pub pk: NodePk,
45}
46
47#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
48pub struct VerifyMsgResponse {
49 pub is_valid: bool,
51}
52
53#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
55pub struct BroadcastedTx {
56 pub txid: Txid,
58 #[serde(with = "hexstr_or_bytes")]
60 pub tx: Vec<u8>,
61
62 pub created_at: TimestampMs,
64}
65
66#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
67pub struct BroadcastedTxInfo {
68 pub txid: Txid,
70 #[serde(with = "hexstr_or_bytes")]
72 pub tx: Vec<u8>,
73 pub created_at: TimestampMs,
75 pub total_outputs: Amount,
77 pub output_destinations: Vec<String>,
79 pub inputs: Vec<String>,
81 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}