@@ -13,7 +13,6 @@ use crate::offer::Offer;
1313use crate :: wallet;
1414use crate :: wallet:: Balance ;
1515use crate :: wallet:: LightningTransaction ;
16- use anyhow:: anyhow;
1716use anyhow:: Context ;
1817use anyhow:: Result ;
1918use flutter_rust_bridge:: StreamSink ;
@@ -22,13 +21,14 @@ use lightning_invoice::Invoice;
2221use lightning_invoice:: InvoiceDescription ;
2322use rust_decimal:: prelude:: ToPrimitive ;
2423use rust_decimal:: Decimal ;
24+ use state:: Storage ;
2525use std:: ops:: Add ;
2626use std:: path:: Path ;
2727use std:: str:: FromStr ;
2828use std:: time:: SystemTime ;
2929use time:: Duration ;
3030pub use time:: OffsetDateTime ;
31- use tokio:: try_join ;
31+ use tokio:: runtime :: Runtime ;
3232
3333pub struct Address {
3434 pub address : String ,
@@ -168,91 +168,114 @@ impl WalletInfo {
168168 Ok ( tx_history)
169169 }
170170}
171+ /// Lazily creates a multi threaded runtime with the the number of worker threads corresponding to
172+ /// the number of available cores.
173+ fn runtime ( ) -> Result < & ' static Runtime > {
174+ static RUNTIME : Storage < Runtime > = Storage :: new ( ) ;
171175
172- #[ tokio:: main( flavor = "current_thread" ) ]
173- pub async fn refresh_wallet_info ( ) -> Result < WalletInfo > {
174- wallet:: sync ( ) ?;
175- WalletInfo :: build_wallet_info ( ) . await
176+ if RUNTIME . try_get ( ) . is_none ( ) {
177+ let runtime = Runtime :: new ( ) ?;
178+ RUNTIME . set ( runtime) ;
179+ }
180+
181+ Ok ( RUNTIME . get ( ) )
182+ }
183+
184+ pub fn refresh_wallet_info ( ) -> Result < WalletInfo > {
185+ runtime ( ) ?. block_on ( async {
186+ wallet:: sync ( ) ?;
187+ WalletInfo :: build_wallet_info ( ) . await
188+ } )
176189}
177190
178- #[ tokio:: main( flavor = "current_thread" ) ]
179- pub async fn run ( stream : StreamSink < Event > , app_dir : String ) -> Result < ( ) > {
191+ pub fn run ( stream : StreamSink < Event > , app_dir : String ) -> Result < ( ) > {
180192 let network = config:: network ( ) ;
181193 anyhow:: ensure!( !app_dir. is_empty( ) , "app_dir must not be empty" ) ;
182- stream. add ( Event :: Init ( format ! ( "Initialising {network} wallet" ) ) ) ;
183- wallet:: init_wallet ( Path :: new ( app_dir. as_str ( ) ) ) ?;
184-
185- stream. add ( Event :: Init ( "Initialising database" . to_string ( ) ) ) ;
186- db:: init_db (
187- & Path :: new ( app_dir. as_str ( ) )
188- . join ( network. to_string ( ) )
189- . join ( "taker.sqlite" ) ,
190- )
191- . await ?;
192-
193- stream. add ( Event :: Init ( "Starting full ldk node" . to_string ( ) ) ) ;
194- let background_processor = wallet:: run_ldk ( ) . await ?;
195-
196- stream. add ( Event :: Init ( "Fetching an offer" . to_string ( ) ) ) ;
197- stream. add ( Event :: Offer ( offer:: get_offer ( ) . await . ok ( ) ) ) ;
198-
199- stream. add ( Event :: Init ( "Fetching your balance" . to_string ( ) ) ) ;
200- stream. add ( Event :: WalletInfo (
201- WalletInfo :: build_wallet_info ( ) . await . ok ( ) ,
202- ) ) ;
203- stream. add ( Event :: Init ( "Checking channel state" . to_string ( ) ) ) ;
204- stream. add ( Event :: ChannelState ( get_channel_state ( ) ) ) ;
205-
206- stream. add ( Event :: Init ( "Ready" . to_string ( ) ) ) ;
207- stream. add ( Event :: Ready ) ;
208-
209- // spawn a connection task keeping the connection with the maker alive.
210- let peer_manager = wallet:: get_peer_manager ( ) ?;
211- let connection_handle = connection:: spawn ( peer_manager) ;
212-
213- // sync offers every 5 seconds
214- let offer_handle = offer:: spawn ( stream. clone ( ) ) ;
215-
216- // sync wallet every 60 seconds
217- let wallet_sync_handle = tokio:: spawn ( async {
218- loop {
219- wallet:: sync ( ) . unwrap_or_else ( |e| tracing:: error!( ?e, "Failed to sync wallet" ) ) ;
220- tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 60 ) ) . await ;
221- }
222- } ) ;
223-
224- // sync wallet info every 10 seconds
225- let wallet_info_stream = stream. clone ( ) ;
226- let wallet_info_sync_handle = tokio:: spawn ( async move {
227- loop {
228- match WalletInfo :: build_wallet_info ( ) . await {
229- Ok ( wallet_info) => {
230- let _ = wallet_info_stream. add ( Event :: WalletInfo ( Some ( wallet_info) ) ) ;
194+ let runtime = runtime ( ) ?;
195+ runtime. block_on ( async move {
196+ stream. add ( Event :: Init ( format ! ( "Initialising {network} wallet" ) ) ) ;
197+ wallet:: init_wallet ( Path :: new ( app_dir. as_str ( ) ) ) ?;
198+
199+ stream. add ( Event :: Init ( "Initialising database" . to_string ( ) ) ) ;
200+ db:: init_db (
201+ & Path :: new ( app_dir. as_str ( ) )
202+ . join ( network. to_string ( ) )
203+ . join ( "taker.sqlite" ) ,
204+ )
205+ . await ?;
206+
207+ stream. add ( Event :: Init ( "Starting full ldk node" . to_string ( ) ) ) ;
208+ let background_processor = wallet:: run_ldk ( ) ?;
209+
210+ stream. add ( Event :: Init ( "Fetching an offer" . to_string ( ) ) ) ;
211+ stream. add ( Event :: Offer ( offer:: get_offer ( ) . await . ok ( ) ) ) ;
212+
213+ stream. add ( Event :: Init ( "Fetching your balance" . to_string ( ) ) ) ;
214+ stream. add ( Event :: WalletInfo (
215+ WalletInfo :: build_wallet_info ( ) . await . ok ( ) ,
216+ ) ) ;
217+ stream. add ( Event :: Init ( "Checking channel state" . to_string ( ) ) ) ;
218+ stream. add ( Event :: ChannelState ( get_channel_state ( ) ) ) ;
219+
220+ stream. add ( Event :: Init ( "Ready" . to_string ( ) ) ) ;
221+ stream. add ( Event :: Ready ) ;
222+
223+ // spawn a connection task keeping the connection with the maker alive.
224+ runtime. spawn ( async move {
225+ let peer_info = config:: maker_peer_info ( ) ;
226+ loop {
227+ let peer_manager = wallet:: get_peer_manager ( ) ;
228+ connection:: connect ( peer_manager, peer_info) . await ;
229+ // add a delay before retrying to connect
230+ tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) . await ;
231+ }
232+ } ) ;
233+
234+ let offer_stream = stream. clone ( ) ;
235+ runtime. spawn ( async move {
236+ loop {
237+ offer_stream. add ( Event :: Offer ( offer:: get_offer ( ) . await . ok ( ) ) ) ;
238+ tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) . await ;
239+ }
240+ } ) ;
241+
242+ runtime. spawn ( async {
243+ loop {
244+ wallet:: sync ( ) . unwrap_or_else ( |e| tracing:: error!( ?e, "Failed to sync wallet" ) ) ;
245+ tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 60 ) ) . await ;
246+ }
247+ } ) ;
248+
249+ let wallet_info_stream = stream. clone ( ) ;
250+ runtime. spawn ( async move {
251+ loop {
252+ match WalletInfo :: build_wallet_info ( ) . await {
253+ Ok ( wallet_info) => {
254+ let _ = wallet_info_stream. add ( Event :: WalletInfo ( Some ( wallet_info) ) ) ;
255+ }
256+ Err ( e) => tracing:: error!( ?e, "Failed to build wallet info" ) ,
231257 }
232- Err ( e ) => tracing :: error! ( ?e , "Failed to build wallet info" ) ,
258+ tokio :: time :: sleep ( std :: time :: Duration :: from_secs ( 10 ) ) . await ;
233259 }
234- tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 10 ) ) . await ;
235- }
236- } ) ;
237-
238- // sync channel state every 5 seconds
239- let channel_state_stream = stream. clone ( ) ;
240- let channel_state_handle = tokio:: spawn ( async move {
241- loop {
242- channel_state_stream. add ( Event :: ChannelState ( get_channel_state ( ) ) ) ;
243- tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) . await ;
244- }
245- } ) ;
260+ } ) ;
246261
247- try_join ! (
248- connection_handle ,
249- offer_handle ,
250- wallet_sync_handle ,
251- wallet_info_sync_handle ,
252- channel_state_handle ,
253- ) ? ;
262+ let channel_state_stream = stream . clone ( ) ;
263+ runtime . spawn ( async move {
264+ loop {
265+ channel_state_stream . add ( Event :: ChannelState ( get_channel_state ( ) ) ) ;
266+ tokio :: time :: sleep ( std :: time :: Duration :: from_secs ( 5 ) ) . await ;
267+ }
268+ } ) ;
254269
255- background_processor. join ( ) . map_err ( |e| anyhow ! ( e) )
270+ runtime. spawn_blocking ( move || {
271+ // background processor joins on a sync thread, meaning that join here will block a
272+ // full thread, which is dis-encouraged to do in async code.
273+ if let Err ( err) = background_processor. join ( ) {
274+ tracing:: error!( ?err, "Background processor stopped unexpected" ) ;
275+ }
276+ } ) ;
277+ Ok ( ( ) )
278+ } )
256279}
257280
258281pub fn get_balance ( ) -> Result < Balance > {
@@ -279,16 +302,18 @@ pub fn network() -> SyncReturn<String> {
279302 SyncReturn ( config:: network ( ) . to_string ( ) )
280303}
281304
282- #[ tokio:: main( flavor = "current_thread" ) ]
283- pub async fn open_channel ( taker_amount : u64 ) -> Result < ( ) > {
284- let peer_info = config:: maker_peer_info ( ) ;
285- wallet:: open_channel ( peer_info, taker_amount) . await
305+ pub fn open_channel ( taker_amount : u64 ) -> Result < ( ) > {
306+ runtime ( ) ?. block_on ( async {
307+ let peer_info = config:: maker_peer_info ( ) ;
308+ wallet:: open_channel ( peer_info, taker_amount) . await
309+ } )
286310}
287311
288- #[ tokio:: main( flavor = "current_thread" ) ]
289- pub async fn close_channel ( ) -> Result < ( ) > {
290- let peer_info = config:: maker_peer_info ( ) ;
291- wallet:: close_channel ( peer_info. pubkey , false ) . await
312+ pub fn close_channel ( ) -> Result < ( ) > {
313+ runtime ( ) ?. block_on ( async {
314+ let peer_info = config:: maker_peer_info ( ) ;
315+ wallet:: close_channel ( peer_info. pubkey , false ) . await
316+ } )
292317}
293318
294319pub fn send_to_address ( address : String , amount : u64 ) -> Result < String > {
@@ -301,42 +326,32 @@ pub fn send_to_address(address: String, amount: u64) -> Result<String> {
301326 Ok ( txid)
302327}
303328
304- #[ tokio:: main( flavor = "current_thread" ) ]
305- pub async fn list_cfds ( ) -> Result < Vec < Cfd > > {
306- let mut conn = db:: acquire ( ) . await ?;
307- cfd:: load_cfds ( & mut conn) . await
329+ pub fn list_cfds ( ) -> Result < Vec < Cfd > > {
330+ runtime ( ) ?. block_on ( async {
331+ let mut conn = db:: acquire ( ) . await ?;
332+ cfd:: load_cfds ( & mut conn) . await
333+ } )
308334}
309335
310- #[ tokio:: main( flavor = "current_thread" ) ]
311- pub async fn open_cfd ( order : Order ) -> Result < ( ) > {
312- cfd:: open ( & order) . await
336+ pub fn open_cfd ( order : Order ) -> Result < ( ) > {
337+ runtime ( ) ?. block_on ( async { cfd:: open ( & order) . await } )
313338}
314339
315- #[ tokio:: main( flavor = "current_thread" ) ]
316- pub async fn call_faucet ( address : String ) -> Result < String > {
340+ pub fn call_faucet ( address : String ) -> Result < String > {
317341 anyhow:: ensure!(
318342 !address. is_empty( ) ,
319343 "Cannot call faucet because of empty address"
320344 ) ;
321- faucet:: call_faucet ( address) . await
345+ runtime ( ) ? . block_on ( async { faucet:: call_faucet ( address) . await } )
322346}
323347
324- #[ tokio:: main( flavor = "current_thread" ) ]
325- pub async fn get_fee_recommendation ( ) -> Result < u32 > {
326- let fee_recommendation = wallet:: get_fee_recommendation ( ) ?;
327-
328- Ok ( fee_recommendation)
348+ pub fn get_fee_recommendation ( ) -> Result < u32 > {
349+ wallet:: get_fee_recommendation ( )
329350}
330351
331352/// Settles a CFD with the given taker and maker amounts in sats
332- #[ tokio:: main( flavor = "current_thread" ) ]
333- pub async fn settle_cfd ( cfd : Cfd , offer : Offer ) -> Result < ( ) > {
334- cfd:: settle ( & cfd, & offer) . await
335- }
336-
337- #[ tokio:: main( flavor = "current_thread" ) ]
338- pub async fn get_lightning_tx_history ( ) -> Result < Vec < LightningTransaction > > {
339- wallet:: get_lightning_history ( ) . await
353+ pub fn settle_cfd ( cfd : Cfd , offer : Offer ) -> Result < ( ) > {
354+ runtime ( ) ?. block_on ( async { cfd:: settle ( & cfd, & offer) . await } )
340355}
341356
342357/// Initialise logging infrastructure for Rust
@@ -350,19 +365,18 @@ pub fn get_seed_phrase() -> Vec<String> {
350365 wallet:: get_seed_phrase ( )
351366}
352367
353- #[ tokio:: main( flavor = "current_thread" ) ]
354- pub async fn send_lightning_payment ( invoice : String ) -> Result < ( ) > {
368+ pub fn send_lightning_payment ( invoice : String ) -> Result < ( ) > {
355369 anyhow:: ensure!( !invoice. is_empty( ) , "Cannot pay empty invoice" ) ;
356- wallet:: send_lightning_payment ( & invoice) . await
370+ runtime ( ) ? . block_on ( async { wallet:: send_lightning_payment ( & invoice) . await } )
357371}
358372
359- #[ tokio:: main( flavor = "current_thread" ) ]
360- pub async fn create_lightning_invoice (
373+ pub fn create_lightning_invoice (
361374 amount_sats : u64 ,
362375 expiry_secs : u32 ,
363376 description : String ,
364377) -> Result < String > {
365- wallet:: create_invoice ( amount_sats, expiry_secs, description) . await
378+ runtime ( ) ?
379+ . block_on ( async { wallet:: create_invoice ( amount_sats, expiry_secs, description) . await } )
366380}
367381
368382// Note, this implementation has to be on the api level as otherwise it wouldn't be generated
0 commit comments