From 089ac0806efa9b3a40a2f5ffc86c58cc99e1853c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 3 Mar 2021 13:05:18 +1100 Subject: [PATCH 1/4] Simplify constructor of Bob's EventLoop We never customize the behaviour or transport. Might as well hide those details in the implementation. --- swap/src/protocol/bob.rs | 15 ++------------- swap/src/protocol/bob/event_loop.rs | 23 ++++++++++++++--------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index f63d358c..ff815260 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -5,10 +5,7 @@ use crate::{ database::Database, execution_params::ExecutionParams, monero, network, - network::{ - peer_tracker::{self, PeerTracker}, - transport::build, - }, + network::peer_tracker::{self, PeerTracker}, protocol::{alice, alice::TransferProof, bob}, seed::Seed, }; @@ -53,7 +50,6 @@ pub struct Swap { pub struct Builder { swap_id: Uuid, identity: Keypair, - peer_id: PeerId, db: Database, alice_address: Multiaddr, @@ -84,12 +80,10 @@ impl Builder { execution_params: ExecutionParams, ) -> Self { let identity = network::Seed::new(seed).derive_libp2p_identity(); - let peer_id = identity.public().into_peer_id(); Self { swap_id, identity, - peer_id, db, alice_address, alice_peer_id, @@ -152,13 +146,8 @@ impl Builder { fn init_event_loop( &self, ) -> Result<(bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle)> { - let bob_behaviour = bob::Behaviour::default(); - let bob_transport = build(&self.identity)?; - bob::event_loop::EventLoop::new( - bob_transport, - bob_behaviour, - self.peer_id, + &self.identity, self.alice_peer_id, self.alice_address.clone(), self.bitcoin_wallet.clone(), diff --git a/swap/src/protocol/bob/event_loop.rs b/swap/src/protocol/bob/event_loop.rs index b7e222a7..44011600 100644 --- a/swap/src/protocol/bob/event_loop.rs +++ b/swap/src/protocol/bob/event_loop.rs @@ -1,7 +1,7 @@ use crate::{ bitcoin, bitcoin::EncryptedSignature, - network::{transport::SwapTransport, TokioExecutor}, + network::{transport, TokioExecutor}, protocol::{ alice::{QuoteResponse, TransferProof}, bob::{Behaviour, OutEvent, QuoteRequest, State0, State2}, @@ -114,18 +114,23 @@ pub struct EventLoop { impl EventLoop { pub fn new( - transport: SwapTransport, - behaviour: Behaviour, - peer_id: PeerId, + identity: &libp2p::core::identity::Keypair, alice_peer_id: PeerId, alice_addr: Multiaddr, bitcoin_wallet: Arc, ) -> Result<(Self, EventLoopHandle)> { - let mut swarm = libp2p::swarm::SwarmBuilder::new(transport, behaviour, peer_id) - .executor(Box::new(TokioExecutor { - handle: tokio::runtime::Handle::current(), - })) - .build(); + let behaviour = Behaviour::default(); + let transport = transport::build(identity)?; + + let mut swarm = libp2p::swarm::SwarmBuilder::new( + transport, + behaviour, + identity.public().into_peer_id(), + ) + .executor(Box::new(TokioExecutor { + handle: tokio::runtime::Handle::current(), + })) + .build(); swarm.add_address(alice_peer_id, alice_addr); From a4c25080b6e08e974dbc75af80bd07d2047f5975 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 3 Mar 2021 13:26:12 +1100 Subject: [PATCH 2/4] Merge network::Seed into crate::Seed This allows us to unify the way we derive new secret key material and simplify the usage of seed by only having a single one. --- swap/src/bin/asb.rs | 2 +- swap/src/bin/swap_cli.rs | 2 +- swap/src/network.rs | 36 +------------------------ swap/src/protocol/alice/event_loop.rs | 3 +-- swap/src/protocol/bob.rs | 4 +-- swap/src/seed.rs | 38 ++++++++++++++++++++++++--- swap/tests/testutils/mod.rs | 2 +- 7 files changed, 41 insertions(+), 46 deletions(-) diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index 15f4e01d..41b6b234 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -89,7 +89,7 @@ async fn main() -> Result<()> { let (bitcoin_wallet, monero_wallet) = init_wallets( config.clone(), &wallet_data_dir, - seed.extended_private_key(BITCOIN_NETWORK)?, + seed.derive_extended_private_key(BITCOIN_NETWORK)?, ) .await?; diff --git a/swap/src/bin/swap_cli.rs b/swap/src/bin/swap_cli.rs index 9e0da9ec..9fded67e 100644 --- a/swap/src/bin/swap_cli.rs +++ b/swap/src/bin/swap_cli.rs @@ -256,7 +256,7 @@ async fn init_bitcoin_wallet( config.bitcoin.electrum_http_url, bitcoin_network, bitcoin_wallet_data_dir, - seed.extended_private_key(bitcoin_network)?, + seed.derive_extended_private_key(bitcoin_network)?, ) .await?; diff --git a/swap/src/network.rs b/swap/src/network.rs index 917b9977..27c898b9 100644 --- a/swap/src/network.rs +++ b/swap/src/network.rs @@ -2,10 +2,8 @@ pub mod peer_tracker; pub mod request_response; pub mod transport; -use crate::seed::SEED_LENGTH; -use bitcoin::hashes::{sha256, Hash, HashEngine}; use futures::prelude::*; -use libp2p::{core::Executor, identity::ed25519}; +use libp2p::core::Executor; use std::pin::Pin; use tokio::runtime::Handle; @@ -19,35 +17,3 @@ impl Executor for TokioExecutor { let _ = self.handle.spawn(future); } } - -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct Seed([u8; SEED_LENGTH]); - -impl Seed { - /// prefix "NETWORK" to the provided seed and apply sha256 - pub fn new(seed: crate::seed::Seed) -> Self { - let mut engine = sha256::HashEngine::default(); - - engine.input(&seed.bytes()); - engine.input(b"NETWORK"); - - let hash = sha256::Hash::from_engine(engine); - Self(hash.into_inner()) - } - - fn bytes(&self) -> [u8; SEED_LENGTH] { - self.0 - } - - pub fn derive_libp2p_identity(&self) -> libp2p::identity::Keypair { - let mut engine = sha256::HashEngine::default(); - - engine.input(&self.bytes()); - engine.input(b"LIBP2P_IDENTITY"); - - let hash = sha256::Hash::from_engine(engine); - let key = - ed25519::SecretKey::from_bytes(hash.into_inner()).expect("we always pass 32 bytes"); - libp2p::identity::Keypair::Ed25519(key.into()) - } -} diff --git a/swap/src/protocol/alice/event_loop.rs b/swap/src/protocol/alice/event_loop.rs index 403f6ecf..3716d2cf 100644 --- a/swap/src/protocol/alice/event_loop.rs +++ b/swap/src/protocol/alice/event_loop.rs @@ -5,7 +5,6 @@ use crate::{ execution_params::ExecutionParams, monero, monero::{Amount, BalanceTooLow}, - network, network::{transport, TokioExecutor}, protocol::{ alice, @@ -113,7 +112,7 @@ where rate_service: RS, max_sell: Amount, ) -> Result<(Self, mpsc::Receiver>>)> { - let identity = network::Seed::new(seed).derive_libp2p_identity(); + let identity = seed.derive_libp2p_identity(); let behaviour = Behaviour::default(); let transport = transport::build(&identity)?; let peer_id = PeerId::from(identity.public()); diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index ff815260..3b4b8954 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -4,7 +4,7 @@ use crate::{ bitcoin, database::Database, execution_params::ExecutionParams, - monero, network, + monero, network::peer_tracker::{self, PeerTracker}, protocol::{alice, alice::TransferProof, bob}, seed::Seed, @@ -79,7 +79,7 @@ impl Builder { alice_peer_id: PeerId, execution_params: ExecutionParams, ) -> Self { - let identity = network::Seed::new(seed).derive_libp2p_identity(); + let identity = seed.derive_libp2p_identity(); Self { swap_id, diff --git a/swap/src/seed.rs b/swap/src/seed.rs index ebcf6135..39235b8d 100644 --- a/swap/src/seed.rs +++ b/swap/src/seed.rs @@ -2,6 +2,8 @@ use crate::fs::ensure_directory_exists; use ::bitcoin::secp256k1::{self, constants::SECRET_KEY_SIZE, SecretKey}; use anyhow::Result; use bdk::bitcoin::util::bip32::ExtendedPrivKey; +use bitcoin::hashes::{sha256, Hash, HashEngine}; +use libp2p::identity; use pem::{encode, Pem}; use rand::prelude::*; use std::{ @@ -28,13 +30,21 @@ impl Seed { Ok(Seed(bytes)) } - pub fn extended_private_key(&self, network: bitcoin::Network) -> Result { - let private_key = ExtendedPrivKey::new_master(network, &self.bytes())?; + pub fn derive_extended_private_key( + &self, + network: bitcoin::Network, + ) -> Result { + let seed = self.derive(b"BITCOIN_EXTENDED_PRIVATE_KEY").bytes(); + let private_key = ExtendedPrivKey::new_master(network, &seed)?; + Ok(private_key) } - pub fn bytes(&self) -> [u8; SEED_LENGTH] { - self.0 + pub fn derive_libp2p_identity(&self) -> identity::Keypair { + let bytes = self.derive(b"NETWORK").derive(b"LIBP2P_IDENTITY").bytes(); + let key = identity::ed25519::SecretKey::from_bytes(bytes).expect("we always pass 32 bytes"); + + identity::Keypair::Ed25519(key.into()) } pub fn from_file_or_generate(data_dir: &Path) -> Result { @@ -53,6 +63,26 @@ impl Seed { Ok(random_seed) } + /// Derive a new seed using the given scope. + /// + /// This function is purposely kept private because it is only a helper + /// function for deriving specific secret material from the root seed + /// like the libp2p identity or the seed for the Bitcoin wallet. + fn derive(&self, scope: &[u8]) -> Self { + let mut engine = sha256::HashEngine::default(); + + engine.input(&self.bytes()); + engine.input(scope); + + let hash = sha256::Hash::from_engine(engine); + + Self(hash.into_inner()) + } + + fn bytes(&self) -> [u8; SEED_LENGTH] { + self.0 + } + fn from_file(seed_file: D) -> Result where D: AsRef, diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 697c28c9..b6494605 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -606,7 +606,7 @@ async fn init_test_wallets( electrum_http_url, bitcoin::Network::Regtest, datadir, - seed.extended_private_key(bitcoin::Network::Regtest) + seed.derive_extended_private_key(bitcoin::Network::Regtest) .expect("Could not create extended private key from seed"), ) .await From 54bc91581f4c26b1e8f836f3d66bca74c9f14569 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 3 Mar 2021 13:36:37 +1100 Subject: [PATCH 3/4] Don't unnecessarily create async blocks If our expression directly evaluates to a future, we don't need to create an async block. This requires us to have `EventLoopRun::run` consume the instance instead of just taking a mutable reference (otherwise we run into lifetime issues). However, that is better anyway because `run` is an endless loop so you never get to use the handle afterwards anyway. --- swap/src/bin/asb.rs | 2 +- swap/src/bin/swap_cli.rs | 4 ++-- swap/src/protocol/alice/event_loop.rs | 2 +- swap/tests/testutils/mod.rs | 10 ++++------ 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/swap/src/bin/asb.rs b/swap/src/bin/asb.rs index 41b6b234..488aade3 100644 --- a/swap/src/bin/asb.rs +++ b/swap/src/bin/asb.rs @@ -100,7 +100,7 @@ async fn main() -> Result<()> { let rate_service = kraken::RateService::new().await?; - let (mut event_loop, _) = EventLoop::new( + let (event_loop, _) = EventLoop::new( config.network.listen, seed, execution_params, diff --git a/swap/src/bin/swap_cli.rs b/swap/src/bin/swap_cli.rs index 9fded67e..0e75c214 100644 --- a/swap/src/bin/swap_cli.rs +++ b/swap/src/bin/swap_cli.rs @@ -149,7 +149,7 @@ async fn main() -> Result<()> { ); let (swap, event_loop) = bob_factory.with_init_params(send_bitcoin).build().await?; - let handle = tokio::spawn(async move { event_loop.run().await }); + let handle = tokio::spawn(event_loop.run()); let swap = bob::run(swap); tokio::select! { event_loop_result = handle => { @@ -193,7 +193,7 @@ async fn main() -> Result<()> { execution_params, ); let (swap, event_loop) = bob_factory.build().await?; - let handle = tokio::spawn(async move { event_loop.run().await }); + let handle = tokio::spawn(event_loop.run()); let swap = bob::run(swap); tokio::select! { event_loop_result = handle => { diff --git a/swap/src/protocol/alice/event_loop.rs b/swap/src/protocol/alice/event_loop.rs index 3716d2cf..a188930f 100644 --- a/swap/src/protocol/alice/event_loop.rs +++ b/swap/src/protocol/alice/event_loop.rs @@ -158,7 +158,7 @@ where self.peer_id } - pub async fn run(&mut self) { + pub async fn run(mut self) { loop { tokio::select! { swarm_event = self.swarm.next().fuse() => { diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index b6494605..844c127e 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -103,7 +103,7 @@ impl TestContext { .await .unwrap(); - let join_handle = tokio::spawn(async move { event_loop.run().await }); + let join_handle = tokio::spawn(event_loop.run()); (swap, BobEventLoopJoinHandle(join_handle)) } @@ -116,7 +116,7 @@ impl TestContext { let (swap, event_loop) = self.bob_params.builder().build().await.unwrap(); - let join_handle = tokio::spawn(async move { event_loop.run().await }); + let join_handle = tokio::spawn(event_loop.run()); (swap, BobEventLoopJoinHandle(join_handle)) } @@ -376,7 +376,7 @@ where ) .await; - let (mut alice_event_loop, alice_swap_handle) = alice::EventLoop::new( + let (alice_event_loop, alice_swap_handle) = alice::EventLoop::new( alice_listen_address.clone(), alice_seed, execution_params, @@ -390,9 +390,7 @@ where let alice_peer_id = alice_event_loop.peer_id(); - tokio::spawn(async move { - alice_event_loop.run().await; - }); + tokio::spawn(alice_event_loop.run()); let bob_params = BobParams { seed: Seed::random().unwrap(), From ce077a3ff5b2a53ec859b86e7682096f059b8e1a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 3 Mar 2021 13:56:25 +1100 Subject: [PATCH 4/4] Decouple Bob's EventLoop from the builder Instead of instantiating the `EventLoop` within the builder, we only pass in the necessary arguments (which is the `EventLoopHandle`) to the Builder upon `new`. This is work towards #255 which will require us to perform network communication (which implies having the `EventLoop`) before starting a swap. --- swap/src/bin/swap_cli.rs | 48 +++++++++++++-------- swap/src/protocol/bob.rs | 83 +++++++++---------------------------- swap/tests/testutils/mod.rs | 26 ++++++++---- 3 files changed, 68 insertions(+), 89 deletions(-) diff --git a/swap/src/bin/swap_cli.rs b/swap/src/bin/swap_cli.rs index 0e75c214..ad6c74dd 100644 --- a/swap/src/bin/swap_cli.rs +++ b/swap/src/bin/swap_cli.rs @@ -31,7 +31,7 @@ use swap::{ monero::{CreateWallet, OpenWallet}, protocol::{ bob, - bob::{cancel::CancelError, Builder}, + bob::{cancel::CancelError, Builder, EventLoop}, }, seed::Seed, }; @@ -111,6 +111,7 @@ async fn main() -> Result<()> { init_bitcoin_wallet(config, bitcoin_network, &wallet_data_dir, seed).await?; let monero_wallet = init_monero_wallet(monero_network, monero_wallet_rpc_process.endpoint()).await?; + let bitcoin_wallet = Arc::new(bitcoin_wallet); let swap_id = Uuid::new_v4(); @@ -137,19 +138,25 @@ async fn main() -> Result<()> { let send_bitcoin = bitcoin_wallet.max_giveable(TxLock::script_size()).await?; - let bob_factory = Builder::new( - seed, + let (event_loop, event_loop_handle) = EventLoop::new( + &seed.derive_libp2p_identity(), + alice_peer_id, + alice_addr, + bitcoin_wallet.clone(), + )?; + let handle = tokio::spawn(event_loop.run()); + + let swap = Builder::new( db, swap_id, - Arc::new(bitcoin_wallet), + bitcoin_wallet.clone(), Arc::new(monero_wallet), - alice_addr, - alice_peer_id, execution_params, - ); - let (swap, event_loop) = bob_factory.with_init_params(send_bitcoin).build().await?; + event_loop_handle, + ) + .with_init_params(send_bitcoin) + .build()?; - let handle = tokio::spawn(event_loop.run()); let swap = bob::run(swap); tokio::select! { event_loop_result = handle => { @@ -181,19 +188,26 @@ async fn main() -> Result<()> { init_bitcoin_wallet(config, bitcoin_network, &wallet_data_dir, seed).await?; let monero_wallet = init_monero_wallet(monero_network, monero_wallet_rpc_process.endpoint()).await?; + let bitcoin_wallet = Arc::new(bitcoin_wallet); - let bob_factory = Builder::new( - seed, + let (event_loop, event_loop_handle) = EventLoop::new( + &seed.derive_libp2p_identity(), + alice_peer_id, + alice_addr, + bitcoin_wallet.clone(), + )?; + let handle = tokio::spawn(event_loop.run()); + + let swap = Builder::new( db, swap_id, - Arc::new(bitcoin_wallet), + bitcoin_wallet.clone(), Arc::new(monero_wallet), - alice_addr, - alice_peer_id, execution_params, - ); - let (swap, event_loop) = bob_factory.build().await?; - let handle = tokio::spawn(event_loop.run()); + event_loop_handle, + ) + .build()?; + let swap = bob::run(swap); tokio::select! { event_loop_result = handle => { diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 3b4b8954..70ecc7d6 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -7,10 +7,9 @@ use crate::{ monero, network::peer_tracker::{self, PeerTracker}, protocol::{alice, alice::TransferProof, bob}, - seed::Seed, }; use anyhow::{Error, Result}; -use libp2p::{core::Multiaddr, identity::Keypair, NetworkBehaviour, PeerId}; +use libp2p::{core::Multiaddr, NetworkBehaviour, PeerId}; use std::sync::Arc; use tracing::debug; use uuid::Uuid; @@ -49,17 +48,15 @@ pub struct Swap { pub struct Builder { swap_id: Uuid, - identity: Keypair, db: Database, - alice_address: Multiaddr, - alice_peer_id: PeerId, - bitcoin_wallet: Arc, monero_wallet: Arc, init_params: InitParams, execution_params: ExecutionParams, + + event_loop_handle: bob::EventLoopHandle, } enum InitParams { @@ -70,27 +67,21 @@ enum InitParams { impl Builder { #[allow(clippy::too_many_arguments)] pub fn new( - seed: Seed, db: Database, swap_id: Uuid, bitcoin_wallet: Arc, monero_wallet: Arc, - alice_address: Multiaddr, - alice_peer_id: PeerId, execution_params: ExecutionParams, + event_loop_handle: bob::EventLoopHandle, ) -> Self { - let identity = seed.derive_libp2p_identity(); - Self { swap_id, - identity, db, - alice_address, - alice_peer_id, bitcoin_wallet, monero_wallet, init_params: InitParams::None, execution_params, + event_loop_handle, } } @@ -101,57 +92,21 @@ impl Builder { } } - pub async fn build(self) -> Result<(bob::Swap, bob::EventLoop)> { - match self.init_params { - InitParams::New { btc_amount } => { - let initial_state = BobState::Started { btc_amount }; + pub fn build(self) -> Result { + let state = match self.init_params { + InitParams::New { btc_amount } => BobState::Started { btc_amount }, + InitParams::None => self.db.get_state(self.swap_id)?.try_into_bob()?.into(), + }; - let (event_loop, event_loop_handle) = self.init_event_loop()?; - - Ok(( - Swap { - state: initial_state, - event_loop_handle, - db: self.db, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - swap_id: self.swap_id, - execution_params: self.execution_params, - }, - event_loop, - )) - } - - InitParams::None => { - let resume_state = self.db.get_state(self.swap_id)?.try_into_bob()?.into(); - - let (event_loop, event_loop_handle) = self.init_event_loop()?; - - Ok(( - Swap { - state: resume_state, - event_loop_handle, - db: self.db, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - swap_id: self.swap_id, - execution_params: self.execution_params, - }, - event_loop, - )) - } - } - } - - fn init_event_loop( - &self, - ) -> Result<(bob::event_loop::EventLoop, bob::event_loop::EventLoopHandle)> { - bob::event_loop::EventLoop::new( - &self.identity, - self.alice_peer_id, - self.alice_address.clone(), - self.bitcoin_wallet.clone(), - ) + Ok(Swap { + state, + event_loop_handle: self.event_loop_handle, + db: self.db, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + swap_id: self.swap_id, + execution_params: self.execution_params, + }) } } diff --git a/swap/tests/testutils/mod.rs b/swap/tests/testutils/mod.rs index 844c127e..61cd01db 100644 --- a/swap/tests/testutils/mod.rs +++ b/swap/tests/testutils/mod.rs @@ -54,16 +54,23 @@ struct BobParams { } impl BobParams { - pub fn builder(&self) -> bob::Builder { + pub fn builder(&self, event_loop_handle: bob::EventLoopHandle) -> bob::Builder { bob::Builder::new( - self.seed, Database::open(&self.db_path.clone().as_path()).unwrap(), self.swap_id, self.bitcoin_wallet.clone(), self.monero_wallet.clone(), - self.alice_address.clone(), - self.alice_peer_id, self.execution_params, + event_loop_handle, + ) + } + + pub fn new_eventloop(&self) -> Result<(bob::EventLoop, bob::EventLoopHandle)> { + bob::EventLoop::new( + &self.seed.derive_libp2p_identity(), + self.alice_peer_id, + self.alice_address.clone(), + self.bitcoin_wallet.clone(), ) } } @@ -95,12 +102,13 @@ pub struct TestContext { impl TestContext { pub async fn new_swap_as_bob(&mut self) -> (bob::Swap, BobEventLoopJoinHandle) { - let (swap, event_loop) = self + let (event_loop, event_loop_handle) = self.bob_params.new_eventloop().unwrap(); + + let swap = self .bob_params - .builder() + .builder(event_loop_handle) .with_init_params(self.btc_amount) .build() - .await .unwrap(); let join_handle = tokio::spawn(event_loop.run()); @@ -114,7 +122,9 @@ impl TestContext { ) -> (bob::Swap, BobEventLoopJoinHandle) { join_handle.abort(); - let (swap, event_loop) = self.bob_params.builder().build().await.unwrap(); + let (event_loop, event_loop_handle) = self.bob_params.new_eventloop().unwrap(); + + let swap = self.bob_params.builder(event_loop_handle).build().unwrap(); let join_handle = tokio::spawn(event_loop.run());