1use std::collections::{HashMap, HashSet};
2use std::pin::Pin;
3use std::time::{Duration, SystemTime};
4use std::{mem, vec};
5
6use anyhow::{Error, Result};
7use chrono::{SecondsFormat, Utc};
8use rand::distributions::uniform::{SampleRange, SampleUniform};
9use rand::distributions::{Alphanumeric, DistString, Standard};
10use rand::rngs::OsRng;
11use rand::{random, Rng};
12use sdk_common::grpc::{
13 self, CreateSwapResponse, PaySwapResponse, RefundSwapResponse, SwapParameters,
14};
15use sdk_common::prelude::{FiatAPI, FiatCurrency, Rate};
16use secp256k1::musig::MusigKeyAggCache;
17use serde_json::{json, Value};
18use tokio::sync::{mpsc, watch, Mutex};
19use tokio::time::sleep;
20use tokio_stream::Stream;
21use tokio_stream::StreamExt;
22use tonic::Status;
23
24use crate::backup::{BackupState, BackupTransport};
25use crate::bitcoin::blockdata::opcodes::all::{
26 OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CSV, OP_EQUALVERIFY, OP_HASH160,
27};
28use crate::bitcoin::blockdata::script;
29use crate::bitcoin::hashes::hex::ToHex;
30use crate::bitcoin::hashes::ripemd160;
31use crate::bitcoin::hashes::{sha256, Hash};
32use crate::bitcoin::secp256k1::ecdsa::RecoverableSignature;
33use crate::bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1};
34use crate::bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey};
35use crate::bitcoin::util::taproot::{TaprootBuilder, TaprootSpendInfo};
36use crate::bitcoin::{Address, Network, Script, Sequence, XOnlyPublicKey};
37use crate::breez_services::{OpenChannelParams, Receiver};
38use crate::buy::BuyBitcoinApi;
39use crate::chain::{ChainService, OnchainTx, Outspend, RecommendedFees, TxStatus};
40use crate::error::{ReceivePaymentError, SdkError, SdkResult};
41use crate::invoice::{InvoiceError, InvoiceResult};
42use crate::lightning::ln::PaymentSecret;
43use crate::lightning_invoice::{Currency, InvoiceBuilder, RawBolt11Invoice};
44use crate::lsp::LspInformation;
45use crate::models::{
46 LnPaymentDetails, LspAPI, NodeState, Payment, PaymentDetails, PaymentStatus, PaymentType,
47 ReverseSwapServiceAPI, SwapperAPI, SyncResponse, TlvEntry,
48};
49use crate::node_api::{
50 CreateInvoiceRequest, FetchBolt11Result, IncomingPayment, NodeAPI, NodeError, NodeResult,
51};
52use crate::swap_in::TaprootSwapperAPI;
53use crate::swap_out::boltzswap::{BoltzApiCreateReverseSwapResponse, BoltzApiReverseSwapStatus};
54use crate::swap_out::error::{ReverseSwapError, ReverseSwapResult};
55use crate::{
56 parse_invoice, BuyBitcoinProvider, Config, CustomMessage, LNInvoice, MaxChannelAmount,
57 NodeCredentials, OpeningFeeParamsMenu, PaymentResponse, PrepareRedeemOnchainFundsRequest,
58 PrepareRedeemOnchainFundsResponse, ReceivePaymentRequest, ReverseSwapPairInfo, RouteHint,
59 RouteHintHop, SwapInfo,
60};
61
62pub const MOCK_REVERSE_SWAP_MIN: u64 = 50_000;
63pub const MOCK_REVERSE_SWAP_MAX: u64 = 1_000_000;
64
65pub struct MockBackupTransport {
66 pub num_pushed: std::sync::Mutex<u32>,
67 pub num_pulled: std::sync::Mutex<u32>,
68 pub remote_version: std::sync::Mutex<Option<u64>>,
69 pub state: std::sync::Mutex<Option<BackupState>>,
70}
71
72impl MockBackupTransport {
73 pub fn new() -> Self {
74 MockBackupTransport {
75 num_pushed: std::sync::Mutex::new(0),
76 num_pulled: std::sync::Mutex::new(0),
77 remote_version: std::sync::Mutex::new(None),
78 state: std::sync::Mutex::new(None),
79 }
80 }
81 pub fn pushed(&self) -> u32 {
82 *self.num_pushed.lock().unwrap()
83 }
84 pub fn pulled(&self) -> u32 {
85 *self.num_pulled.lock().unwrap()
86 }
87}
88
89#[tonic::async_trait]
90impl BackupTransport for MockBackupTransport {
91 async fn pull(&self) -> SdkResult<Option<BackupState>> {
92 sleep(Duration::from_millis(10)).await;
93 *self.num_pulled.lock().unwrap() += 1;
94 let current_state = self.state.lock().unwrap();
95
96 match current_state.clone() {
97 Some(state) => Ok(Some(state)),
98 None => Ok(None),
99 }
100 }
101 async fn push(&self, version: Option<u64>, data: Vec<u8>) -> SdkResult<u64> {
102 sleep(Duration::from_millis(10)).await;
103 let mut remote_version = self.remote_version.lock().unwrap();
104 let mut numpushed = self.num_pushed.lock().unwrap();
105 *numpushed += 1;
106
107 if !remote_version.is_none() && *remote_version != version {
108 return Err(SdkError::Generic {
109 err: "version mismatch".into(),
110 });
111 }
112 let next_version = match version {
113 Some(v) => v + 1,
114 None => 1,
115 };
116 *remote_version = Some(next_version);
117 *self.state.lock().unwrap() = Some(BackupState {
118 generation: next_version,
119 data,
120 });
121 Ok(next_version)
122 }
123}
124
125pub struct MockSwapperAPI {}
126
127#[tonic::async_trait]
128impl SwapperAPI for MockSwapperAPI {
129 async fn complete_swap(&self, _bolt11: String) -> Result<()> {
130 Ok(())
131 }
132}
133
134pub struct MockReverseSwapperAPI {}
135
136#[tonic::async_trait]
137impl ReverseSwapServiceAPI for MockReverseSwapperAPI {
138 async fn fetch_reverse_swap_fees(&self) -> ReverseSwapResult<ReverseSwapPairInfo> {
139 Ok(ReverseSwapPairInfo {
140 min: MOCK_REVERSE_SWAP_MIN,
141 max: MOCK_REVERSE_SWAP_MAX,
142 fees_hash: rand_string(5),
143 fees_percentage: 0.5,
144 fees_lockup: 3_000 + rand_int_in_range(1..1_000),
145 fees_claim: 3_000 + rand_int_in_range(1..1_000),
146 total_fees: None,
147 })
148 }
149
150 async fn create_reverse_swap_on_remote(
151 &self,
152 _amount_sat: u64,
153 _preimage_hash_hex: String,
154 _claim_pubkey: String,
155 _pair_hash: String,
156 _routing_node: String,
157 ) -> ReverseSwapResult<BoltzApiCreateReverseSwapResponse> {
158 Err(ReverseSwapError::generic("Not implemented"))
159 }
160
161 async fn get_boltz_status(&self, _id: String) -> ReverseSwapResult<BoltzApiReverseSwapStatus> {
162 Err(ReverseSwapError::generic("Not implemented"))
163 }
164
165 async fn get_route_hints(&self, _routing_node_id: String) -> ReverseSwapResult<Vec<RouteHint>> {
166 Err(ReverseSwapError::generic("Not implemented"))
167 }
168}
169
170#[derive(Clone)]
171pub struct MockChainService {
172 pub tip: u32,
173 pub recommended_fees: RecommendedFees,
174 pub address_to_transactions: HashMap<String, Vec<OnchainTx>>,
175}
176
177impl Default for MockChainService {
178 fn default() -> Self {
179 let recommended_fees: RecommendedFees = serde_json::from_str(
180 r#"{
181 "fastestFee": 1,
182 "halfHourFee": 1,
183 "hourFee": 1,
184 "economyFee": 1,
185 "minimumFee": 1
186 }"#,
187 )
188 .unwrap();
189
190 let txs: Vec<OnchainTx> = serde_json::from_str(
191 r#"[{"txid":"a418e856bb22b6345868dc0b1ac1dd7a6b7fae1d231b275b74172f9584fa0bdf","version":1,"locktime":0,"vin":[{"txid":"ec901bcab07df7d475d98fff2933dcb56d57bbdaa029c4142aed93462b6928fe","vout":0,"prevout":{"scriptpubkey":"0014b34b7da80e662d1db3fcfbe34b7f4cacc4fac34d","scriptpubkey_asm":"OP_0 OP_PUSHBYTES_20 b34b7da80e662d1db3fcfbe34b7f4cacc4fac34d","scriptpubkey_type":"v0_p2wpkh","scriptpubkey_address":"bc1qkd9hm2qwvck3mvlul035kl6v4nz04s6dmryeq5","value":197497253},"scriptsig":"","scriptsig_asm":"","witness":["304502210089933e46614114e060d3d681c54af71e3d47f8be8131d9310ef8fe231c060f3302204103910a6790e3a678964df6f0f9ae2107666a91e777bd87f9172a28653e374701","0356f385879fefb8c52758126f6e7b9ac57374c2f73f2ee9047b4c61df0ba390b9"],"is_coinbase":false,"sequence":4294967293},{"txid":"fda3ce37f5fb849502e2027958d51efebd1841cb43bbfdd5f3d354c93a551ef9","vout":0,"prevout":{"scriptpubkey":"00145c7f3b6ceb79d03d5a5397df83f2334394ebdd2c","scriptpubkey_asm":"OP_0 OP_PUSHBYTES_20 5c7f3b6ceb79d03d5a5397df83f2334394ebdd2c","scriptpubkey_type":"v0_p2wpkh","scriptpubkey_address":"bc1qt3lnkm8t08gr6kjnjl0c8u3ngw2whhfvzwsxrg","value":786885},"scriptsig":"","scriptsig_asm":"","witness":["304402200ae5465efe824609f7faf1094cce0195763df52e5409dd9ae0526568bf3bcaa20220103749041a87e082cf95bf1e12c5174881e5e4c55e75ab2db29a68538dbabbad01","03dfd8cc1f72f46d259dc0afc6d756bce551fce2fbf58a9ad36409a1b82a17e64f"],"is_coinbase":false,"sequence":4294967293}],"vout":[{"scriptpubkey":"a9141df45814863edfd6d87457e8f8bd79607a116a8f87","scriptpubkey_asm":"OP_HASH160 OP_PUSHBYTES_20 1df45814863edfd6d87457e8f8bd79607a116a8f OP_EQUAL","scriptpubkey_type":"p2sh","scriptpubkey_address":"34RQERthXaruAXtW6q1bvrGTeUbqi2Sm1i","value":26087585},{"scriptpubkey":"001479001aa5f4b981a0b654c3f834d0573595b0ed53","scriptpubkey_asm":"OP_0 OP_PUSHBYTES_20 79001aa5f4b981a0b654c3f834d0573595b0ed53","scriptpubkey_type":"v0_p2wpkh","scriptpubkey_address":"bc1q0yqp4f05hxq6pdj5c0urf5zhxk2mpm2ndx85za","value":171937413}],"size":372,"weight":837,"fee":259140,"status":{"confirmed":true,"block_height":767637,"block_hash":"000000000000000000077769f3b2e6a28b9ed688f0d773f9ff2d73c622a2cfac","block_time":1671174562}},{"txid":"ec901bcab07df7d475d98fff2933dcb56d57bbdaa029c4142aed93462b6928fe","version":1,"locktime":767636,"vin":[{"txid":"d4344fc9e7f66b3a1a50d1d76836a157629ba0c6ede093e94f1c809d334c9146","vout":0,"prevout":{"scriptpubkey":"0014cab22290b7adc75f861de820baa97d319c1110a6","scriptpubkey_asm":"OP_0 OP_PUSHBYTES_20 cab22290b7adc75f861de820baa97d319c1110a6","scriptpubkey_type":"v0_p2wpkh","scriptpubkey_address":"bc1qe2ez9y9h4hr4lpsaaqst42taxxwpzy9xlzqt8k","value":209639471},"scriptsig":"","scriptsig_asm":"","witness":["304402202e914c35b75da798f0898c7cfe6ead207aaee41219afd77124fd56971f05d9030220123ce5d124f4635171b7622995dae35e00373a5fbf8117bfdca5e5080ad6554101","02122fa6d20413bb5da5c7e3fb42228be5436b1bd84e29b294bfc200db5eac460e"],"is_coinbase":false,"sequence":4294967293}],"vout":[{"scriptpubkey":"0014b34b7da80e662d1db3fcfbe34b7f4cacc4fac34d","scriptpubkey_asm":"OP_0 OP_PUSHBYTES_20 b34b7da80e662d1db3fcfbe34b7f4cacc4fac34d","scriptpubkey_type":"v0_p2wpkh","scriptpubkey_address":"bc1qkd9hm2qwvck3mvlul035kl6v4nz04s6dmryeq5","value":197497253},{"scriptpubkey":"0014f0e2a057d0e60411ac3d7218e29bf9489a59df18","scriptpubkey_asm":"OP_0 OP_PUSHBYTES_20 f0e2a057d0e60411ac3d7218e29bf9489a59df18","scriptpubkey_type":"v0_p2wpkh","scriptpubkey_address":"bc1q7r32q47suczprtpawgvw9xlefzd9nhccyatxvu","value":12140465}],"size":222,"weight":561,"fee":1753,"status":{"confirmed":true,"block_height":767637,"block_hash":"000000000000000000077769f3b2e6a28b9ed688f0d773f9ff2d73c622a2cfac","block_time":1671174562}}]"#,
192 ).unwrap();
193 Self {
194 tip: 767640,
195 recommended_fees,
196 address_to_transactions: HashMap::from([(
197 "bc1qkd9hm2qwvck3mvlul035kl6v4nz04s6dmryeq5".to_string(),
198 txs,
199 )]),
200 }
201 }
202}
203
204#[tonic::async_trait]
205impl ChainService for MockChainService {
206 async fn recommended_fees(&self) -> SdkResult<RecommendedFees> {
207 Ok(self.recommended_fees.clone())
208 }
209
210 async fn address_transactions(&self, address: String) -> SdkResult<Vec<OnchainTx>> {
211 Ok(self
212 .address_to_transactions
213 .get(&address)
214 .unwrap_or(&Vec::<OnchainTx>::new())
215 .clone())
216 }
217
218 async fn current_tip(&self) -> SdkResult<u32> {
219 Ok(self.tip)
220 }
221
222 async fn transaction_outspends(&self, _txid: String) -> SdkResult<Vec<Outspend>> {
223 Ok(vec![Outspend {
224 spent: true,
225 txid: Some("test-tx-id".into()),
226 vin: Some(0),
227 status: Some(TxStatus {
228 confirmed: true,
229 block_height: Some(123),
230 block_hash: Some("test-hash".into()),
231 block_time: Some(123),
232 }),
233 }])
234 }
235
236 async fn broadcast_transaction(&self, _tx: Vec<u8>) -> SdkResult<String> {
237 let mut array = [0; 32];
238 rand::thread_rng().fill(&mut array);
239 Ok(hex::encode(array))
240 }
241}
242
243impl TryFrom<Payment> for crate::models::PaymentResponse {
244 type Error = anyhow::Error;
245
246 fn try_from(payment: Payment) -> std::result::Result<Self, Self::Error> {
247 let payment_hash: String = match payment.details.clone() {
248 crate::models::PaymentDetails::Ln { data } => data.payment_hash,
249 _ => "".into(),
250 };
251 let payment_preimage: String = match payment.details.clone() {
252 crate::models::PaymentDetails::Ln { data } => data.payment_preimage,
253 _ => "".into(),
254 };
255 Ok(crate::models::PaymentResponse {
256 payment_time: payment.payment_time,
257 amount_msat: payment.amount_msat,
258 fee_msat: payment.fee_msat,
259 payment_hash,
260 payment_preimage,
261 })
262 }
263}
264
265pub struct MockReceiver {
266 pub bolt11: String,
267}
268
269impl Default for MockReceiver {
270 fn default() -> Self {
271 MockReceiver { bolt11: "lnbc500u1p3eerl2dq8w3jhxaqpp5w3w4z63erts5usxtkvpwdy356l29xfd43mnzlq6x2d69kqhjtepsxqyjw5qsp5an4vlkhp8cgahvamrdkn2uzmmcd5neq7yq3j6a8v0sc0q9rlde5s9qrsgqcqpxrzjqwk7573qcyfskzw33jnvs0shq9tzy28sd86naqlgkdga9p8z74fsyzancsqqvpsqqqqqqqlgqqqqqzsqygrzjqwk7573qcyfskzw33jnvs0shq9tzy28sd86naqlgkdga9p8z74fsyqqqqyqqqqqqqqqqqqlgqqqqqzsqjqacpq7rd5rf7ssza0lps93ehylrwtjhdlk44g0llwp039f8uqxsck52ccr69djxs59mmwqkvvglylpg0cdzaqusg9m9cyju92t7kjpfsqma2lmf".to_string() }
272 }
273}
274
275#[tonic::async_trait]
276impl Receiver for MockReceiver {
277 fn open_channel_needed(&self, _amount_msat: u64) -> Result<bool, ReceivePaymentError> {
278 Ok(true)
279 }
280 async fn receive_payment(
281 &self,
282 _request: ReceivePaymentRequest,
283 ) -> Result<crate::ReceivePaymentResponse, ReceivePaymentError> {
284 Ok(crate::ReceivePaymentResponse {
285 ln_invoice: parse_invoice(&self.bolt11)?,
286 opening_fee_params: _request.opening_fee_params,
287 opening_fee_msat: None,
288 })
289 }
290 async fn wrap_node_invoice(
291 &self,
292 invoice: &str,
293 _params: Option<OpenChannelParams>,
294 _lsp_info: Option<LspInformation>,
295 ) -> Result<String, ReceivePaymentError> {
296 Ok(String::from(invoice))
297 }
298}
299
300pub struct MockNodeAPI {
301 cloud_payments: Mutex<Vec<Payment>>,
307 node_state: NodeState,
308 on_send_custom_message: Box<dyn Fn(CustomMessage) -> NodeResult<()> + Sync + Send>,
309 on_stream_custom_messages: Mutex<mpsc::Receiver<CustomMessage>>,
310}
311
312#[tonic::async_trait]
313impl NodeAPI for MockNodeAPI {
314 async fn node_credentials(&self) -> NodeResult<Option<NodeCredentials>> {
315 Err(NodeError::Generic("Not implemented".to_string()))
316 }
317
318 async fn configure_node(&self, _close_to_address: Option<String>) -> NodeResult<()> {
319 Ok(())
320 }
321
322 async fn create_invoice(&self, request: CreateInvoiceRequest) -> NodeResult<String> {
323 let invoice = create_invoice(
324 request.description,
325 request.amount_msat,
326 vec![],
327 request.preimage,
328 );
329 Ok(invoice.bolt11)
330 }
331
332 async fn delete_invoice(&self, _bolt11: String) -> NodeResult<()> {
333 Ok(())
334 }
335
336 async fn pull_changed(
337 &self,
338 _sync_state: Option<Value>,
339 _match_local_balance: bool,
340 ) -> NodeResult<SyncResponse> {
341 Ok(SyncResponse {
342 sync_state: Value::Null,
343 node_state: self.node_state.clone(),
344 payments: self
345 .cloud_payments
346 .lock()
347 .await
348 .iter()
349 .cloned()
350 .flat_map(TryInto::try_into)
351 .collect(),
352 channels: Vec::new(),
353 })
354 }
355
356 async fn send_pay(&self, _bolt11: String, _max_hops: u32) -> NodeResult<PaymentResponse> {
357 Err(NodeError::Generic("Not implemented".to_string()))
358 }
359
360 async fn send_payment(
361 &self,
362 bolt11: String,
363 _amount_msat: Option<u64>,
364 _label: Option<String>,
365 ) -> NodeResult<Payment> {
366 let payment = self.add_dummy_payment_for(bolt11, None, None).await?;
367 Ok(payment)
368 }
369
370 async fn send_trampoline_payment(
371 &self,
372 bolt11: String,
373 _amount_msat: u64,
374 _label: Option<String>,
375 _trampoline_id: Vec<u8>,
376 ) -> NodeResult<Payment> {
377 let payment = self.add_dummy_payment_for(bolt11, None, None).await?;
378 Ok(payment)
379 }
380
381 async fn send_spontaneous_payment(
382 &self,
383 _node_id: String,
384 _amount_msat: u64,
385 _extra_tlvs: Option<Vec<TlvEntry>>,
386 _label: Option<String>,
387 ) -> NodeResult<Payment> {
388 let payment = self.add_dummy_payment_rand().await?;
389 Ok(payment)
390 }
391
392 async fn node_id(&self) -> NodeResult<String> {
393 Ok("".to_string())
394 }
395
396 async fn redeem_onchain_funds(
397 &self,
398 _to_address: String,
399 _sat_per_vbyte: u32,
400 ) -> NodeResult<Vec<u8>> {
401 Ok(rand_vec_u8(32))
402 }
403
404 async fn prepare_redeem_onchain_funds(
405 &self,
406 _req: PrepareRedeemOnchainFundsRequest,
407 ) -> NodeResult<PrepareRedeemOnchainFundsResponse> {
408 Err(NodeError::Generic("Not implemented".to_string()))
409 }
410
411 async fn start_signer(&self, _shutdown: mpsc::Receiver<()>) {}
412
413 async fn start_keep_alive(&self, _shutdown: watch::Receiver<()>) {}
414
415 async fn connect_peer(&self, _node_id: String, _addr: String) -> NodeResult<()> {
416 Ok(())
417 }
418
419 async fn sign_message(&self, _message: &str) -> NodeResult<String> {
420 Ok("".to_string())
421 }
422
423 async fn check_message(
424 &self,
425 _message: &str,
426 _pubkey: &str,
427 _signature: &str,
428 ) -> NodeResult<bool> {
429 Ok(true)
430 }
431
432 async fn sign_invoice(&self, invoice: RawBolt11Invoice) -> NodeResult<String> {
433 Ok(sign_invoice(invoice))
434 }
435
436 async fn close_peer_channels(&self, _node_id: String) -> NodeResult<Vec<String>> {
437 Ok(vec![])
438 }
439 async fn stream_incoming_payments(
440 &self,
441 ) -> NodeResult<Pin<Box<dyn Stream<Item = IncomingPayment> + Send>>> {
442 Err(NodeError::Generic("Not implemented".to_string()))
443 }
444
445 async fn stream_log_messages(&self) -> NodeResult<Pin<Box<dyn Stream<Item = String> + Send>>> {
446 Err(NodeError::Generic("Not implemented".to_string()))
447 }
448
449 async fn static_backup(&self) -> NodeResult<Vec<String>> {
450 Ok(Vec::new())
451 }
452
453 async fn execute_command(&self, _command: String) -> NodeResult<Value> {
454 Err(NodeError::Generic("Not implemented".to_string()))
455 }
456
457 async fn generate_diagnostic_data(&self) -> NodeResult<Value> {
458 Ok(json!({}))
459 }
460
461 async fn max_sendable_amount<'a>(
462 &self,
463 _payee_node_id: Option<Vec<u8>>,
464 _max_hops: u32,
465 _last_hop: Option<&'a RouteHintHop>,
466 ) -> NodeResult<Vec<MaxChannelAmount>> {
467 Err(NodeError::Generic("Not implemented".to_string()))
468 }
469
470 async fn derive_bip32_key(&self, _path: Vec<ChildNumber>) -> NodeResult<ExtendedPrivKey> {
471 Ok(ExtendedPrivKey::new_master(Network::Bitcoin, &[])?)
472 }
473
474 async fn legacy_derive_bip32_key(
475 &self,
476 _path: Vec<ChildNumber>,
477 ) -> NodeResult<ExtendedPrivKey> {
478 Ok(ExtendedPrivKey::new_master(Network::Bitcoin, &[])?)
479 }
480
481 async fn send_custom_message(&self, message: CustomMessage) -> NodeResult<()> {
482 (self.on_send_custom_message)(message)
483 }
484
485 async fn stream_custom_messages(
486 &self,
487 ) -> NodeResult<Pin<Box<dyn Stream<Item = core::result::Result<CustomMessage, Error>> + Send>>>
488 {
489 let (_, next_rx) = mpsc::channel(1);
490 let mut guard = self.on_stream_custom_messages.lock().await;
491 let rx = mem::replace(&mut *guard, next_rx);
492 Ok(Box::pin(
493 tokio_stream::wrappers::ReceiverStream::new(rx).map(Ok),
494 ))
495 }
496
497 async fn get_routing_hints(
498 &self,
499 _lsp_info: &LspInformation,
500 ) -> NodeResult<(Vec<RouteHint>, bool)> {
501 Ok((vec![], false))
502 }
503
504 async fn fetch_bolt11(&self, _payment_hash: Vec<u8>) -> NodeResult<Option<FetchBolt11Result>> {
505 Ok(None)
506 }
507
508 async fn get_open_peers(&self) -> NodeResult<HashSet<Vec<u8>>> {
509 Ok(HashSet::new())
510 }
511}
512
513impl MockNodeAPI {
514 pub fn new(node_state: NodeState) -> Self {
515 Self {
516 cloud_payments: Mutex::new(Vec::new()),
517 node_state,
518 on_send_custom_message: Box::new(|_| Ok(())),
519 on_stream_custom_messages: {
520 let (_, rx) = mpsc::channel(1);
521 Mutex::new(rx)
522 },
523 }
524 }
525 pub(crate) async fn add_dummy_payment_for(
531 &self,
532 bolt11: String,
533 preimage: Option<sha256::Hash>,
534 status: Option<PaymentStatus>,
535 ) -> NodeResult<Payment> {
536 let inv = bolt11
537 .parse::<crate::lightning_invoice::Bolt11Invoice>()
538 .map_err(|e| NodeError::Generic(e.to_string()))?;
539
540 Ok(self.add_dummy_payment(inv, preimage, status).await)
541 }
542
543 pub(crate) async fn add_dummy_payment_rand(&self) -> NodeResult<Payment> {
545 let preimage = sha256::Hash::hash(&rand_vec_u8(10));
546 let inv = rand_invoice_with_description_hash_and_preimage("test".into(), preimage)?;
547
548 Ok(self.add_dummy_payment(inv, Some(preimage), None).await)
549 }
550
551 pub(crate) async fn add_dummy_payment(
553 &self,
554 inv: crate::lightning_invoice::Bolt11Invoice,
555 preimage: Option<sha256::Hash>,
556 status: Option<PaymentStatus>,
557 ) -> Payment {
558 let preimage = match preimage {
559 Some(preimage) => preimage.to_hex(),
560 None => hex::encode(rand_vec_u8(32)),
561 };
562 let payment = Payment {
563 id: inv.payment_hash().to_hex(),
564 payment_type: PaymentType::Sent,
565 payment_time: random::<i64>(),
566 amount_msat: inv.amount_milli_satoshis().unwrap_or_default(),
567 fee_msat: 0,
568 status: status.unwrap_or(PaymentStatus::Complete),
569 error: None,
570 description: None,
571 details: PaymentDetails::Ln {
572 data: LnPaymentDetails {
573 payment_hash: inv.payment_hash().to_hex(),
574 label: String::new(),
575 destination_pubkey: hex::encode(rand_vec_u8(32)),
576 payment_preimage: preimage,
577 keysend: false,
578 bolt11: inv.to_string(),
579 lnurl_success_action: None,
580 lnurl_pay_domain: None,
581 lnurl_pay_comment: None,
582 lnurl_metadata: None,
583 ln_address: None,
584 lnurl_withdraw_endpoint: None,
585 swap_info: None,
586 reverse_swap_info: None,
587 pending_expiration_block: None,
588 open_channel_bolt11: None,
589 },
590 },
591 metadata: None,
592 };
593
594 self.save_payment_for_future_sync_updates(payment).await
595 }
596
597 async fn save_payment_for_future_sync_updates(&self, payment: Payment) -> Payment {
599 let mut cloud_payments = self.cloud_payments.lock().await;
600
601 match cloud_payments.iter().find(|p| p.id == payment.id) {
606 None => {
607 cloud_payments.push(payment.clone());
609 payment
610 }
611 Some(p) => {
612 p.clone()
616 }
617 }
618 }
619
620 pub fn set_on_send_custom_message(
621 &mut self,
622 f: Box<dyn Fn(CustomMessage) -> NodeResult<()> + Sync + Send>,
623 ) {
624 self.on_send_custom_message = f;
625 }
626
627 pub async fn set_on_stream_custom_messages(&mut self, f: mpsc::Receiver<CustomMessage>) {
628 *self.on_stream_custom_messages.lock().await = f;
629 }
630}
631
632pub struct MockBreezServer {}
633
634impl MockBreezServer {
635 pub(crate) fn lsp_pub_key(&self) -> String {
636 "02d4e7e420d9dcf6f0206c27ecc69c400cc269b1f5f5ec856d8c9d1fc7e6d910d6".to_string()
637 }
638 pub(crate) fn lsp_id(&self) -> String {
639 "1".to_string()
640 }
641}
642
643#[tonic::async_trait]
644impl LspAPI for MockBreezServer {
645 async fn list_lsps(&self, _node_pubkey: String) -> SdkResult<Vec<LspInformation>> {
646 Ok(vec![LspInformation {
647 id: self.lsp_id(),
648 name: "test lsp".to_string(),
649 widget_url: "".to_string(),
650 pubkey: self.lsp_pub_key(),
651 host: "localhost".to_string(),
652 base_fee_msat: 1,
653 fee_rate: 1.0,
654 time_lock_delta: 32,
655 min_htlc_msat: 1000,
656 lsp_pubkey: hex::decode(self.lsp_pub_key()).unwrap(),
657 opening_fee_params_list: OpeningFeeParamsMenu::try_from(vec![get_test_ofp_48h(
661 10, 12,
662 )])?,
663 }])
664 }
665
666 async fn list_used_lsps(&self, _node_pubkey: String) -> SdkResult<Vec<LspInformation>> {
667 Ok(vec![])
668 }
669
670 async fn register_payment_notifications(
671 &self,
672 _lsp_id: String,
673 _lsp_pubkey: Vec<u8>,
674 _webhook_url: String,
675 _webhook_url_signature: String,
676 ) -> SdkResult<grpc::RegisterPaymentNotificationResponse> {
677 Ok(grpc::RegisterPaymentNotificationResponse {})
678 }
679
680 async fn unregister_payment_notifications(
681 &self,
682 _lsp_id: String,
683 _lsp_pubkey: Vec<u8>,
684 _webhook_url: String,
685 _webhook_url_signature: String,
686 ) -> SdkResult<grpc::RemovePaymentNotificationResponse> {
687 Ok(grpc::RemovePaymentNotificationResponse {})
688 }
689
690 async fn register_payment(
691 &self,
692 _lsp_id: String,
693 _lsp_pubkey: Vec<u8>,
694 _payment_info: grpc::PaymentInformation,
695 ) -> SdkResult<grpc::RegisterPaymentReply> {
696 Ok(grpc::RegisterPaymentReply {})
697 }
698}
699
700#[tonic::async_trait]
701impl FiatAPI for MockBreezServer {
702 async fn list_fiat_currencies(&self) -> Result<Vec<FiatCurrency>> {
703 Ok(vec![])
704 }
705
706 async fn fetch_fiat_rates(&self) -> Result<Vec<Rate>> {
707 Ok(vec![Rate {
708 coin: "USD".to_string(),
709 value: 20_000.00,
710 }])
711 }
712}
713
714pub struct MockBuyBitcoinService {}
715
716#[tonic::async_trait]
717impl BuyBitcoinApi for MockBuyBitcoinService {
718 async fn buy_bitcoin(
719 &self,
720 _provider: BuyBitcoinProvider,
721 swap_info: &SwapInfo,
722 _redirect_url: Option<String>,
723 ) -> Result<String> {
724 Ok(format!(
725 "https://mock.moonpay?wa={}&ma={}",
726 swap_info.bitcoin_address.as_str(),
727 format!("{:.8}", swap_info.max_allowed_deposit as f64 / 100000000.0).as_str(),
728 ))
729 }
730}
731
732pub(crate) fn rand_invoice_with_description_hash(
733 expected_desc: String,
734) -> InvoiceResult<crate::lightning_invoice::Bolt11Invoice> {
735 let preimage = sha256::Hash::hash(&rand_vec_u8(10));
736
737 rand_invoice_with_description_hash_and_preimage(expected_desc, preimage)
738}
739
740pub(crate) fn rand_invoice_with_description_hash_and_preimage(
741 expected_desc: String,
742 preimage: sha256::Hash,
743) -> InvoiceResult<crate::lightning_invoice::Bolt11Invoice> {
744 let expected_desc_hash = Hash::hash(expected_desc.as_bytes());
745
746 let hashed_preimage = Message::from_hashed_data::<sha256::Hash>(&preimage[..]);
747 let payment_hash = hashed_preimage.as_ref();
748
749 let payment_secret = PaymentSecret([42u8; 32]);
750
751 let secp = Secp256k1::new();
752 let key_pair = KeyPair::new(&secp, &mut rand::thread_rng());
753 let private_key = key_pair.secret_key();
754
755 Ok(InvoiceBuilder::new(Currency::Bitcoin)
756 .description_hash(expected_desc_hash)
757 .amount_milli_satoshis(50 * 1000)
758 .payment_hash(
759 Hash::from_slice(payment_hash).map_err(|e| InvoiceError::Generic(e.to_string()))?,
760 )
761 .payment_secret(payment_secret)
762 .current_timestamp()
763 .min_final_cltv_expiry_delta(144)
764 .build_signed(|hash| Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key))?)
765}
766
767pub fn rand_string(len: usize) -> String {
768 Alphanumeric.sample_string(&mut rand::thread_rng(), len)
769}
770
771pub fn rand_vec_u8(len: usize) -> Vec<u8> {
772 rand::thread_rng().sample_iter(Standard).take(len).collect()
773}
774
775pub fn rand_int_in_range<T, R>(range: R) -> T
776where
777 T: SampleUniform,
778 R: SampleRange<T>,
779{
780 rand::thread_rng().gen_range(range)
781}
782
783pub fn create_test_config() -> crate::models::Config {
784 let mut conf = Config {
785 default_lsp_id: Some(String::from("03cea51f-b654-4fb0-8e82-eca137f236a0")),
786 chainnotifier_url: "http://test-chainnotifier.local".to_string(),
787 ..Config::production(
788 "".into(),
789 crate::NodeConfig::Greenlight {
790 config: crate::GreenlightNodeConfig {
791 partner_credentials: None,
792 invite_code: None,
793 },
794 },
795 )
796 };
797 conf.working_dir = get_test_working_dir();
798 conf
799}
800
801pub(crate) fn create_test_persister(
802 config: crate::models::Config,
803) -> crate::persist::db::SqliteStorage {
804 println!("create_test_persister {}", config.working_dir);
805 crate::persist::db::SqliteStorage::new(config.working_dir)
806}
807
808pub fn get_test_working_dir() -> String {
809 let mut rng = rand::thread_rng();
810 let s = std::env::temp_dir().to_str().unwrap().to_string();
811 let dir = format!("{}/{}", s, rng.gen::<u32>());
812 std::fs::create_dir_all(dir.clone()).unwrap();
813 dir
814}
815
816pub fn create_invoice(
817 description: String,
818 amount_msat: u64,
819 hints: Vec<RouteHint>,
820 invoice_preimage: Option<Vec<u8>>,
821) -> LNInvoice {
822 let preimage = invoice_preimage.unwrap_or(rand::thread_rng().gen::<[u8; 32]>().to_vec());
823 let hashed = Message::from_hashed_data::<sha256::Hash>(&preimage[..]);
824 let hash = hashed.as_ref();
825
826 let mut invoice_builder = InvoiceBuilder::new(Currency::Bitcoin)
827 .description(description)
828 .payment_hash(sha256::Hash::hash(hash))
829 .timestamp(SystemTime::now())
830 .amount_milli_satoshis(amount_msat)
831 .expiry_time(Duration::new(3600, 0))
832 .payment_secret(PaymentSecret(rand::thread_rng().gen::<[u8; 32]>()))
833 .min_final_cltv_expiry_delta(32);
834
835 for hint in hints {
836 invoice_builder = invoice_builder.private_route(hint.to_ldk_hint().unwrap());
837 }
838
839 let raw_invoice = invoice_builder.build_raw().unwrap();
840 parse_invoice(&sign_invoice(raw_invoice)).unwrap()
841}
842
843fn sign_invoice(invoice: RawBolt11Invoice) -> String {
844 let secp = Secp256k1::new();
845 let (secret_key, _) = secp.generate_keypair(&mut OsRng);
846 invoice
847 .sign(|m| -> Result<RecoverableSignature, anyhow::Error> {
848 Ok(secp.sign_ecdsa_recoverable(m, &secret_key))
849 })
850 .unwrap()
851 .to_string()
852}
853
854pub(crate) fn get_test_ofp_48h(min_msat: u64, proportional: u32) -> grpc::OpeningFeeParams {
856 get_test_ofp_generic(min_msat, proportional, true, chrono::Duration::days(10))
857}
858
859pub(crate) fn get_test_ofp(
861 min_msat: u64,
862 proportional: u32,
863 future_or_past: bool,
864) -> grpc::OpeningFeeParams {
865 get_test_ofp_generic(
866 min_msat,
867 proportional,
868 future_or_past,
869 chrono::Duration::seconds(60),
870 )
871}
872
873pub(crate) fn get_test_ofp_generic(
874 min_msat: u64,
875 proportional: u32,
876 future_or_past: bool,
877 duration: chrono::Duration,
878) -> grpc::OpeningFeeParams {
879 let now = Utc::now();
880 let date_time = match future_or_past {
881 true => now.checked_add_signed(duration).unwrap(),
882 false => now.checked_sub_signed(duration).unwrap(),
883 };
884 let formatted = date_time.to_rfc3339_opts(SecondsFormat::Millis, true);
885
886 grpc::OpeningFeeParams {
887 min_msat,
888 proportional,
889 valid_until: formatted,
890 max_idle_time: 0,
891 max_client_to_self_delay: 0,
892 promise: "".to_string(),
893 }
894 .into()
895}
896
897#[tonic::async_trait]
898impl TaprootSwapperAPI for MockBreezServer {
899 async fn create_swap(
900 &self,
901 hash: Vec<u8>,
902 refund_pubkey: Vec<u8>,
903 ) -> SdkResult<CreateSwapResponse> {
904 let lock_time = 1008;
905 let claim_pubkey_bytes =
906 hex::decode("0207121ffeda98fb0b28258d06efaa97623d1b4298dce269630432b9c12f4f6c84")
907 .unwrap();
908 let claim_pubkey = PublicKey::from_slice(&claim_pubkey_bytes).unwrap();
909 let refund_pubkey = PublicKey::from_slice(&refund_pubkey).unwrap();
910 let (x_only_claim_pubkey, _) = claim_pubkey.x_only_public_key();
911 let (x_only_refund_pubkey, _) = refund_pubkey.x_only_public_key();
912 let claim_script = claim_script(&x_only_claim_pubkey, &hash);
913 let refund_script = refund_script(&x_only_refund_pubkey, lock_time);
914
915 let taproot_spend_info = taproot_spend_info(
916 &claim_pubkey.serialize(),
917 &refund_pubkey.serialize(),
918 claim_script,
919 refund_script,
920 )?;
921 let address =
922 Address::p2tr_tweaked(taproot_spend_info.output_key(), Network::Bitcoin).to_string();
923
924 Ok(CreateSwapResponse {
925 address: address.to_string(),
926 claim_pubkey: claim_pubkey_bytes,
927 lock_time,
928 parameters: Some(SwapParameters {
929 max_swap_amount_sat: 1_000_000,
930 min_swap_amount_sat: 1_000,
931 min_utxo_amount_sat: 1_000,
932 }),
933 })
934 }
935 async fn pay_swap(&self, _payment_request: String) -> Result<PaySwapResponse, Status> {
936 Ok(PaySwapResponse::default())
937 }
938 async fn refund_swap(
939 &self,
940 _address: String,
941 _input_index: u32,
942 _pub_nonce: Vec<u8>,
943 _transaction: Vec<u8>,
944 ) -> SdkResult<RefundSwapResponse> {
945 Ok(RefundSwapResponse::default())
946 }
947 async fn swap_parameters(&self) -> SdkResult<Option<SwapParameters>> {
948 Ok(Some(SwapParameters::default()))
949 }
950}
951
952fn claim_script(x_only_claim_pubkey: &XOnlyPublicKey, hash: &[u8]) -> Script {
953 script::Builder::new()
954 .push_opcode(OP_HASH160)
955 .push_slice(&ripemd160::Hash::hash(hash))
956 .push_opcode(OP_EQUALVERIFY)
957 .push_x_only_key(x_only_claim_pubkey)
958 .push_opcode(OP_CHECKSIG)
959 .into_script()
960}
961
962fn refund_script(x_only_refund_pubkey: &XOnlyPublicKey, lock_time: u32) -> Script {
963 script::Builder::new()
964 .push_x_only_key(x_only_refund_pubkey)
965 .push_opcode(OP_CHECKSIGVERIFY)
966 .push_int(Sequence::from_height(lock_time as u16).to_consensus_u32() as i64)
967 .push_opcode(OP_CSV)
968 .into_script()
969}
970
971fn key_agg_cache(claim_pubkey: &[u8], refund_pubkey: &[u8]) -> Result<MusigKeyAggCache> {
972 let cp = secp256k1::PublicKey::from_slice(claim_pubkey)?;
973 let rp = secp256k1::PublicKey::from_slice(refund_pubkey)?;
974 Ok(MusigKeyAggCache::new(
975 &secp256k1::Secp256k1::new(),
976 &[&cp, &rp],
977 ))
978}
979
980fn taproot_spend_info(
981 claim_pubkey: &[u8],
982 refund_pubkey: &[u8],
983 claim_script: Script,
984 refund_script: Script,
985) -> Result<TaprootSpendInfo> {
986 let m = key_agg_cache(claim_pubkey, refund_pubkey)?;
987 let internal_key = m.agg_pk();
988
989 let internal_key = XOnlyPublicKey::from_slice(&internal_key.serialize())?;
991
992 Ok(TaprootBuilder::new()
994 .add_leaf(1, claim_script)?
995 .add_leaf(1, refund_script)?
996 .finalize(&Secp256k1::new(), internal_key)
997 .map_err(|_| anyhow::anyhow!("taproot builder error"))?)
998}