ln.rs
1 use std::sync::{Arc, Mutex}; 2 use std::time::Duration; 3 4 use async_stream::stream; 5 use async_trait::async_trait; 6 use bitcoin::hashes::{sha256, Hash}; 7 use bitcoin::key::KeyPair; 8 use bitcoin::secp256k1::{PublicKey, SecretKey}; 9 use fedimint_core::task::TaskGroup; 10 use fedimint_core::util::BoxStream; 11 use fedimint_core::{secp256k1, Amount}; 12 use fedimint_logging::LOG_TEST; 13 use lightning_invoice::{ 14 Bolt11Invoice, Bolt11InvoiceDescription, Currency, Description, InvoiceBuilder, PaymentSecret, 15 SignedRawBolt11Invoice, DEFAULT_EXPIRY_TIME, 16 }; 17 use ln_gateway::gateway_lnrpc::{ 18 self, CreateInvoiceRequest, CreateInvoiceResponse, EmptyResponse, GetFundingAddressResponse, 19 GetNodeInfoResponse, GetRouteHintsResponse, InterceptHtlcResponse, PayInvoiceRequest, 20 PayInvoiceResponse, 21 }; 22 use ln_gateway::lightning::cln::{HtlcResult, RouteHtlcStream}; 23 use ln_gateway::lightning::{ChannelInfo, ILnRpcClient, LightningRpcError}; 24 use rand::rngs::OsRng; 25 use tokio::sync::mpsc; 26 use tracing::info; 27 28 pub const INVALID_INVOICE_DESCRIPTION: &str = "INVALID"; 29 30 #[derive(Debug)] 31 pub struct FakeLightningTest { 32 pub gateway_node_pub_key: secp256k1::PublicKey, 33 gateway_node_sec_key: secp256k1::SecretKey, 34 amount_sent: Arc<Mutex<u64>>, 35 receiver: mpsc::Receiver<HtlcResult>, 36 } 37 38 impl FakeLightningTest { 39 pub fn new() -> Self { 40 info!(target: LOG_TEST, "Setting up fake lightning test fixture"); 41 let ctx = bitcoin::secp256k1::Secp256k1::new(); 42 let kp = KeyPair::new(&ctx, &mut OsRng); 43 let amount_sent = Arc::new(Mutex::new(0)); 44 let (_, receiver) = mpsc::channel::<HtlcResult>(10); 45 46 FakeLightningTest { 47 gateway_node_sec_key: SecretKey::from_keypair(&kp), 48 gateway_node_pub_key: PublicKey::from_keypair(&kp), 49 amount_sent, 50 receiver, 51 } 52 } 53 } 54 55 impl Default for FakeLightningTest { 56 fn default() -> Self { 57 Self::new() 58 } 59 } 60 61 impl FakeLightningTest { 62 pub async fn invoice( 63 &self, 64 amount: Amount, 65 expiry_time: Option<u64>, 66 ) -> ln_gateway::Result<Bolt11Invoice> { 67 let ctx = bitcoin::secp256k1::Secp256k1::new(); 68 69 Ok(InvoiceBuilder::new(Currency::Regtest) 70 .description("".to_string()) 71 .payment_hash(sha256::Hash::hash(&[0; 32])) 72 .current_timestamp() 73 .min_final_cltv_expiry_delta(0) 74 .payment_secret(PaymentSecret([0; 32])) 75 .amount_milli_satoshis(amount.msats) 76 .expiry_time(Duration::from_secs( 77 expiry_time.unwrap_or(DEFAULT_EXPIRY_TIME), 78 )) 79 .build_signed(|m| ctx.sign_ecdsa_recoverable(m, &self.gateway_node_sec_key)) 80 .unwrap()) 81 } 82 83 /// Creates an invoice that is not payable 84 /// 85 /// * Mocks use hard-coded invoice description to fail the payment 86 /// * Real fixtures won't be able to route to randomly generated node pubkey 87 pub fn unpayable_invoice(&self, amount: Amount, expiry_time: Option<u64>) -> Bolt11Invoice { 88 let ctx = bitcoin::secp256k1::Secp256k1::new(); 89 // Generate fake node keypair 90 let kp = KeyPair::new(&ctx, &mut OsRng); 91 92 // `FakeLightningTest` will fail to pay any invoice with 93 // `INVALID_INVOICE_DESCRIPTION` in the description of the invoice. 94 InvoiceBuilder::new(Currency::Regtest) 95 .payee_pub_key(kp.public_key()) 96 .description(INVALID_INVOICE_DESCRIPTION.to_string()) 97 .payment_hash(sha256::Hash::hash(&[0; 32])) 98 .current_timestamp() 99 .min_final_cltv_expiry_delta(0) 100 .payment_secret(PaymentSecret([0; 32])) 101 .amount_milli_satoshis(amount.msats) 102 .expiry_time(Duration::from_secs( 103 expiry_time.unwrap_or(DEFAULT_EXPIRY_TIME), 104 )) 105 .build_signed(|m| ctx.sign_ecdsa_recoverable(m, &SecretKey::from_keypair(&kp))) 106 .expect("Invoice creation failed") 107 } 108 109 pub fn listening_address(&self) -> String { 110 "FakeListeningAddress".to_string() 111 } 112 } 113 114 #[async_trait] 115 impl ILnRpcClient for FakeLightningTest { 116 async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError> { 117 Ok(GetNodeInfoResponse { 118 pub_key: self.gateway_node_pub_key.serialize().to_vec(), 119 alias: "FakeLightningNode".to_string(), 120 network: "regtest".to_string(), 121 block_height: 0, 122 synced_to_chain: false, 123 }) 124 } 125 126 async fn routehints( 127 &self, 128 _num_route_hints: usize, 129 ) -> Result<GetRouteHintsResponse, LightningRpcError> { 130 Ok(GetRouteHintsResponse { 131 route_hints: vec![gateway_lnrpc::get_route_hints_response::RouteHint { hops: vec![] }], 132 }) 133 } 134 135 async fn pay( 136 &self, 137 invoice: PayInvoiceRequest, 138 ) -> Result<PayInvoiceResponse, LightningRpcError> { 139 let signed = invoice.invoice.parse::<SignedRawBolt11Invoice>().unwrap(); 140 let invoice = Bolt11Invoice::from_signed(signed).unwrap(); 141 *self.amount_sent.lock().unwrap() += invoice.amount_milli_satoshis().unwrap(); 142 143 if invoice.description() 144 == Bolt11InvoiceDescription::Direct( 145 &Description::new(INVALID_INVOICE_DESCRIPTION.into()).unwrap(), 146 ) 147 { 148 return Err(LightningRpcError::FailedPayment { 149 failure_reason: "Description was invalid".to_string(), 150 }); 151 } 152 153 Ok(PayInvoiceResponse { 154 preimage: [0; 32].to_vec(), 155 }) 156 } 157 158 async fn route_htlcs<'a>( 159 mut self: Box<Self>, 160 task_group: &mut TaskGroup, 161 ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError> { 162 let handle = task_group.make_handle(); 163 let shutdown_receiver = handle.make_shutdown_rx().await; 164 165 // `FakeLightningTest` will never intercept any HTLCs because there is no 166 // lightning connection, so instead we just create a stream that blocks 167 // until the task group is shutdown. 168 let stream: BoxStream<'a, HtlcResult> = Box::pin(stream! { 169 shutdown_receiver.await; 170 if let Some(htlc_result) = self.receiver.recv().await { 171 yield htlc_result; 172 } 173 }); 174 Ok((stream, Arc::new(Self::new()))) 175 } 176 177 async fn complete_htlc( 178 &self, 179 _htlc: InterceptHtlcResponse, 180 ) -> Result<EmptyResponse, LightningRpcError> { 181 Ok(EmptyResponse {}) 182 } 183 184 async fn create_invoice( 185 &self, 186 create_invoice_request: CreateInvoiceRequest, 187 ) -> Result<CreateInvoiceResponse, LightningRpcError> { 188 let ctx = bitcoin::secp256k1::Secp256k1::new(); 189 190 let payment_hash = sha256::Hash::from_slice(&create_invoice_request.payment_hash) 191 .expect("Failed to lookup FederationId"); 192 let invoice = InvoiceBuilder::new(Currency::Regtest) 193 .description("".to_string()) 194 .payment_hash(payment_hash) 195 .current_timestamp() 196 .min_final_cltv_expiry_delta(0) 197 .payment_secret(PaymentSecret([0; 32])) 198 .amount_milli_satoshis(create_invoice_request.amount_msat) 199 .expiry_time(Duration::from_secs(create_invoice_request.expiry as u64)) 200 .build_signed(|m| ctx.sign_ecdsa_recoverable(m, &self.gateway_node_sec_key)) 201 .unwrap(); 202 203 Ok(CreateInvoiceResponse { 204 invoice: invoice.to_string(), 205 }) 206 } 207 208 async fn connect_to_peer( 209 &self, 210 _pubkey: bitcoin::secp256k1::PublicKey, 211 _host: String, 212 ) -> Result<EmptyResponse, LightningRpcError> { 213 unimplemented!("FakeLightningTest does not support connecting to peers") 214 } 215 216 async fn get_funding_address(&self) -> Result<GetFundingAddressResponse, LightningRpcError> { 217 unimplemented!("FakeLightningTest does not support getting a funding address") 218 } 219 220 async fn open_channel( 221 &self, 222 _pubkey: bitcoin::secp256k1::PublicKey, 223 _channel_size_sats: u64, 224 _push_amount_sats: u64, 225 ) -> Result<EmptyResponse, LightningRpcError> { 226 unimplemented!("FakeLightningTest does not support opening channels") 227 } 228 229 async fn list_active_channels(&self) -> Result<Vec<ChannelInfo>, LightningRpcError> { 230 unimplemented!("FakeLightningTest does not support listing active channels") 231 } 232 }