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