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
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 pub txid: Txid,
80 #[serde(with = "hexstr_or_bytes")]
82 pub tx: Vec<u8>,
83 pub created_at: TimestampMs,
85 pub total_outputs: Amount,
87 pub output_destinations: Vec<String>,
89 pub inputs: Vec<String>,
91 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}