From b500fb9ae1efc8015640a6c6e6ad0661d41ed1cd Mon Sep 17 00:00:00 2001 From: "Bill.W" <0xbillw@gmail.com> Date: Sun, 4 Feb 2024 03:36:14 +0000 Subject: [PATCH 1/3] feat: provide Ceseal public key query and authentication service --- crates/cestory/api/build.rs | 1 + crates/cestory/api/proto/pubkeys.proto | 45 +++++++++++++ crates/cestory/api/src/lib.rs | 4 ++ crates/cestory/src/expert.rs | 7 ++ crates/cestory/src/lib.rs | 5 +- crates/cestory/src/pubkeys.rs | 91 ++++++++++++++++++++++++++ crates/cestory/src/storage.rs | 4 ++ crates/cestory/src/system/keyfairy.rs | 8 +++ crates/cestory/src/system/mod.rs | 6 +- crates/cestory/src/types.rs | 2 + crates/cestory/tests/test_pubkeys.rs | 35 ++++++++++ 11 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 crates/cestory/api/proto/pubkeys.proto create mode 100644 crates/cestory/src/pubkeys.rs create mode 100644 crates/cestory/tests/test_pubkeys.rs diff --git a/crates/cestory/api/build.rs b/crates/cestory/api/build.rs index 0314e0c3..eab5e52b 100644 --- a/crates/cestory/api/build.rs +++ b/crates/cestory/api/build.rs @@ -35,4 +35,5 @@ fn main() { tonic_build::compile_protos("proto/pois-api.proto").unwrap(); tonic_build::compile_protos("proto/podr2-api.proto").unwrap(); + tonic_build::compile_protos("proto/pubkeys.proto").unwrap(); } diff --git a/crates/cestory/api/proto/pubkeys.proto b/crates/cestory/api/proto/pubkeys.proto new file mode 100644 index 00000000..3666ce97 --- /dev/null +++ b/crates/cestory/api/proto/pubkeys.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +package ceseal.pubkeys; + +// Provide the storage miners with Ceseal's various public key queries +service CesealPubkeysProvider { + // Get the Ceseal identity public key + rpc get_identity_pubkey(Request) returns (IdentityPubkeyResponse) {} + // Get the master public key + rpc get_master_pubkey(Request) returns (MasterPubkeyResponse) {} + // Get the PORD2 public key + rpc get_podr2_pubkey(Request) returns (Podr2PubkeyResponse) {} +} + +message Request { + // The account id that the storage miner registered on the chain + bytes storage_miner_account_id = 1; +} + +message IdentityPubkeyResponse { + // the identity public key + bytes pubkey = 1; + // The timestamp for the processing of the request + int64 timestamp = 2; + // Use the sr25519 algorithm to sign the timestamp fields above (use it's Big-Endian bytes) + bytes signature = 3; +} + +message MasterPubkeyResponse { + // the master public key + bytes pubkey = 1; + // The timestamp for the processing of the request + int64 timestamp = 2; + // Use the sr25519 algorithm to sign the timestamp fields above (use it's Big-Endian bytes) + bytes signature = 3; +} + +message Podr2PubkeyResponse { + // the PODR2 public key + bytes pubkey = 1; + // The timestamp for the processing of the request + int64 timestamp = 2; + // Use the RSA algorithm to sign the timestamp fields above (use it's Big-Endian bytes) + bytes signature = 3; +} \ No newline at end of file diff --git a/crates/cestory/api/src/lib.rs b/crates/cestory/api/src/lib.rs index 8827937d..c76e9b28 100644 --- a/crates/cestory/api/src/lib.rs +++ b/crates/cestory/api/src/lib.rs @@ -22,3 +22,7 @@ pub mod pois { pub mod podr2 { tonic::include_proto!("podr2"); } + +pub mod pubkeys { + tonic::include_proto!("ceseal.pubkeys"); +} \ No newline at end of file diff --git a/crates/cestory/src/expert.rs b/crates/cestory/src/expert.rs index 90dbc3e2..e139f127 100644 --- a/crates/cestory/src/expert.rs +++ b/crates/cestory/src/expert.rs @@ -1,3 +1,5 @@ +use crate::types::MasterKey; + use super::{ pal::Platform, system::WorkerIdentityKey, types::ThreadPoolSafeBox, CesealProperties, CesealSafeBox, ChainStorage, }; @@ -99,6 +101,10 @@ impl CesealExpertStub { self.ceseal_props.identity_key.public() } + pub fn master_key(&self) -> &MasterKey { + &self.ceseal_props.master_key + } + pub fn podr2_key(&self) -> &ces_pdp::Keys { &self.ceseal_props.podr2_key } @@ -180,6 +186,7 @@ mod test { let ceseal_props = CesealProperties { role: WorkerRole::Full, podr2_key: any_podr2_key(), + master_key: new_sr25519_key(), identity_key: any_identity_key(), cores: 2, }; diff --git a/crates/cestory/src/lib.rs b/crates/cestory/src/lib.rs index 60c1a4e3..0fac3605 100644 --- a/crates/cestory/src/lib.rs +++ b/crates/cestory/src/lib.rs @@ -58,6 +58,7 @@ pub mod expert; mod light_validation; pub mod podr2; pub mod pois; +mod pubkeys; mod secret_channel; mod storage; mod system; @@ -862,15 +863,17 @@ async fn run_external_server( let pois_srv = pois::new_pois_certifier_api_server(pois_param.clone(), ceseal_expert.clone()) .max_decoding_message_size(MAX_DECODED_MSG_SIZE) .max_encoding_message_size(MAX_ENCODED_MSG_SIZE); - let poisv_srv = pois::new_pois_verifier_api_server(pois_param, ceseal_expert) + let poisv_srv = pois::new_pois_verifier_api_server(pois_param, ceseal_expert.clone()) .max_decoding_message_size(MAX_DECODED_MSG_SIZE) .max_encoding_message_size(MAX_ENCODED_MSG_SIZE); + let pubkeys = pubkeys::new_pubkeys_provider_server(ceseal_expert); info!( "keyfairy ready, external server will listening on {} run with {:?} role", public_listener_addr, ceseal_props.role ); let mut server = Server::builder(); + server.add_service(pubkeys); let router = match ceseal_props.role { ces_types::WorkerRole::Full => server .add_service(podr2_srv) diff --git a/crates/cestory/src/pubkeys.rs b/crates/cestory/src/pubkeys.rs new file mode 100644 index 00000000..9aff65d2 --- /dev/null +++ b/crates/cestory/src/pubkeys.rs @@ -0,0 +1,91 @@ +use crate::expert::CesealExpertStub; +use cestory_api::pubkeys::{ + ceseal_pubkeys_provider_server::{ + CesealPubkeysProvider, CesealPubkeysProviderServer as CesealPubkeysProviderServerPb, + }, + IdentityPubkeyResponse, MasterPubkeyResponse, Podr2PubkeyResponse, Request as InnerReq, +}; +use sp_core::{crypto::AccountId32, ByteArray, Pair}; +use std::result::Result as StdResult; +use tonic::{Request, Response, Status}; + +pub type Result = StdResult, Status>; +pub type CesealPubkeysProviderServer = CesealPubkeysProviderServerPb; + +pub struct CesealPubkeysProviderImpl { + ceseal_expert: CesealExpertStub, +} + +pub fn new_pubkeys_provider_server(ceseal_expert: CesealExpertStub) -> CesealPubkeysProviderServer { + CesealPubkeysProviderServerPb::new(CesealPubkeysProviderImpl { ceseal_expert }) +} + +async fn is_storage_miner_registered_on_chain( + ceseal_expert: &CesealExpertStub, + account_id: &[u8], +) -> StdResult<(), Status> { + let account_id = AccountId32::from_slice(account_id).map_err(|_| Status::internal("invalid input account"))?; + let registered = ceseal_expert + .using_chain_storage(move |opt| { + if let Some(cs) = opt { + cs.is_storage_miner_registered_ignore_state(account_id) + } else { + false + } + }) + .await + .map_err(|e| Status::internal(format!("internal error: {}", e.to_string())))?; + if !registered { + return Err(Status::internal("the storage miner is not registered on the chain")) + } + Ok(()) +} + +#[tonic::async_trait] +impl CesealPubkeysProvider for CesealPubkeysProviderImpl { + async fn get_identity_pubkey(&self, request: Request) -> Result { + let request = request.into_inner(); + is_storage_miner_registered_on_chain(&self.ceseal_expert, &request.storage_miner_account_id[..]).await?; + let now_ts = chrono::Utc::now().timestamp_millis(); + let pubkey = self.ceseal_expert.identify_public_key(); + let sign = self.ceseal_expert.identity_key().sign(&now_ts.to_be_bytes()); + Ok(Response::new(IdentityPubkeyResponse { + pubkey: pubkey.to_raw_vec(), + timestamp: now_ts, + signature: sign.0.to_vec(), + })) + } + + async fn get_master_pubkey(&self, request: Request) -> Result { + let request = request.into_inner(); + is_storage_miner_registered_on_chain(&self.ceseal_expert, &request.storage_miner_account_id[..]).await?; + let now_ts = chrono::Utc::now().timestamp_millis(); + let pubkey = self.ceseal_expert.master_key().public(); + let sign = self.ceseal_expert.master_key().sign(&now_ts.to_be_bytes()); + Ok(Response::new(MasterPubkeyResponse { + pubkey: pubkey.to_raw_vec(), + timestamp: now_ts, + signature: sign.0.to_vec(), + })) + } + + async fn get_podr2_pubkey(&self, request: Request) -> Result { + use rsa::{pkcs1::EncodeRsaPublicKey, Pkcs1v15Sign}; + + let request = request.into_inner(); + is_storage_miner_registered_on_chain(&self.ceseal_expert, &request.storage_miner_account_id[..]).await?; + let now_ts = chrono::Utc::now().timestamp_millis(); + let pubkey = self.ceseal_expert.podr2_key().pkey.clone(); + let pubkey = pubkey + .to_pkcs1_der() + .map_err(|e| Status::internal(format!("PKCS#1-encoding Podr2 public key error: {:?}", e)))? + .to_vec(); + let sign = self + .ceseal_expert + .podr2_key() + .skey + .sign(Pkcs1v15Sign::new_raw(), &now_ts.to_be_bytes()) + .map_err(|e| Status::internal(format!("Podr2 key sign error: {:?}", e)))?; + Ok(Response::new(Podr2PubkeyResponse { pubkey, timestamp: now_ts, signature: sign })) + } +} diff --git a/crates/cestory/src/storage.rs b/crates/cestory/src/storage.rs index 12518880..b43ba8c1 100644 --- a/crates/cestory/src/storage.rs +++ b/crates/cestory/src/storage.rs @@ -136,6 +136,10 @@ mod storage_ext { self.execute_with(|| pallet_sminer::pallet::Pallet::miner_items(miner_account_id)) } + pub fn is_storage_miner_registered_ignore_state(&self, miner_account_id: AccountId) -> bool { + self.get_storage_miner_info(miner_account_id).is_some() + } + /// Return `None` if given ceseal hash is not allowed on-chain pub(crate) fn get_ceseal_bin_added_at(&self, runtime_hash: &[u8]) -> Option { self.execute_with(|| pallet_tee_worker::CesealBinAddedAt::::get(runtime_hash)) diff --git a/crates/cestory/src/system/keyfairy.rs b/crates/cestory/src/system/keyfairy.rs index a1c28d88..6e6cf9d4 100644 --- a/crates/cestory/src/system/keyfairy.rs +++ b/crates/cestory/src/system/keyfairy.rs @@ -279,4 +279,12 @@ where pub fn rsa_private_key(&self) -> &rsa::RsaPrivateKey { &self.rsa_key } + + pub fn master_key(&self) -> &sr25519::Pair { + &self.master_key + } + + pub fn podr2_key_pair(&self) -> ces_pdp::Keys { + ces_pdp::gen_keypair_from_private_key(self.rsa_key.clone()) + } } diff --git a/crates/cestory/src/system/mod.rs b/crates/cestory/src/system/mod.rs index e6d625f9..3f65e801 100644 --- a/crates/cestory/src/system/mod.rs +++ b/crates/cestory/src/system/mod.rs @@ -302,11 +302,11 @@ impl System { pub(crate) fn send_keyfairy_ready(&mut self) { let keyfairy_ready_sender = self.keyfairy_ready_sender.take(); if let Some(sender) = keyfairy_ready_sender { + let keyfairy = self.keyfairy.as_ref().expect("keyfairy not ready"); let ceseal_props = CesealProperties { role: self.args.role.clone(), - podr2_key: ces_pdp::gen_keypair_from_private_key( - self.keyfairy.as_ref().expect("keyfairy not ready").rsa_private_key().clone(), - ), + podr2_key: keyfairy.podr2_key_pair(), + master_key: keyfairy.master_key().clone(), identity_key: self.identity_key.clone(), cores: self.args.cores, }; diff --git a/crates/cestory/src/types.rs b/crates/cestory/src/types.rs index 2b9e2466..a71cc9ac 100644 --- a/crates/cestory/src/types.rs +++ b/crates/cestory/src/types.rs @@ -14,11 +14,13 @@ extern crate runtime as chain; pub type KeyfairyReadySender = oneshot::Sender; pub type KeyfairyReadyReceiver = oneshot::Receiver; pub type ThreadPoolSafeBox = Arc>; +pub type MasterKey = sp_core::sr25519::Pair; #[derive(Clone)] pub struct CesealProperties { pub role: WorkerRole, pub podr2_key: ces_pdp::Keys, + pub master_key: MasterKey, pub identity_key: WorkerIdentityKey, pub cores: u32, } diff --git a/crates/cestory/tests/test_pubkeys.rs b/crates/cestory/tests/test_pubkeys.rs new file mode 100644 index 00000000..b2df1ce7 --- /dev/null +++ b/crates/cestory/tests/test_pubkeys.rs @@ -0,0 +1,35 @@ +use cestory_api::pubkeys::{ceseal_pubkeys_provider_client::CesealPubkeysProviderClient, Request as InnerReq}; +use sp_core::{crypto::AccountId32, ByteArray}; +use std::str::FromStr; +use tonic::Request; + +#[tokio::test] +async fn fetch_pubkeys() { + let miner_id = AccountId32::from_str("cXjEPD6CAnjupMaRrxq9AEKCA3HRCumSxkSxWVKcL3pMeEyFi").unwrap(); + let mut client = CesealPubkeysProviderClient::connect("http://127.0.0.1:19999").await.unwrap(); + + { + let resp = client + .get_identity_pubkey(Request::new(InnerReq { storage_miner_account_id: miner_id.to_raw_vec() })) + .await + .expect("identity key must be fetch"); + // assert!(resp); + println!("identity pubkey response: {resp:?}"); + } + + { + let resp = client + .get_master_pubkey(Request::new(InnerReq { storage_miner_account_id: miner_id.to_raw_vec() })) + .await + .expect("master pubkey must be fetch"); + println!("master pubkey response: {resp:?}"); + } + + { + let resp = client + .get_podr2_pubkey(Request::new(InnerReq { storage_miner_account_id: miner_id.to_raw_vec() })) + .await + .expect("podr2 pubkey must be fetch"); + println!("podr2 pubkey response: {resp:?}"); + } +} From ebe3ef0ff111cd39e9921261b7d0ef3dafffa3ff Mon Sep 17 00:00:00 2001 From: Demos Chiang <69138672+democ98@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:45:56 +0800 Subject: [PATCH 2/3] fix:ceseal stuck in somewhere when miner request it for tag (#305) --- crates/ces-pdp/src/lib.rs | 9 ++---- crates/cestory/src/lib.rs | 2 +- crates/cestory/src/podr2.rs | 63 ++++++++++++++++++++++++++++--------- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/crates/ces-pdp/src/lib.rs b/crates/ces-pdp/src/lib.rs index d80107c8..9c24d5c2 100644 --- a/crates/ces-pdp/src/lib.rs +++ b/crates/ces-pdp/src/lib.rs @@ -138,17 +138,14 @@ pub fn gen_keypair(bits: usize) -> Keys { // ).unwrap()).unwrap(); let skey = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); let pkey = RsaPublicKey::from(&skey); - println!("{:?}", hex::encode(pkey.to_pkcs1_der().unwrap().to_vec())); + println!("Podr2 public key is {:?}", hex::encode(pkey.to_pkcs1_der().unwrap().to_vec())); Keys { skey, pkey } } pub fn gen_keypair_from_private_key(skey: RsaPrivateKey) -> Keys { let pkey = skey.to_public_key(); - Keys{ - skey, - pkey, - } + Keys { skey, pkey } } use rsa::{ @@ -169,7 +166,7 @@ impl Keys { .sign(Pkcs1v15Sign::new_raw(), data) .map_err(|e| PDPError { error_code: FailCode::ParameterError(e.to_string()) }) } - + pub fn verify_data(&self, hashed: &[u8], sig: &[u8]) -> Result<(), PDPError> { self.pkey .verify(Pkcs1v15Sign::new_raw(), hashed, sig) diff --git a/crates/cestory/src/lib.rs b/crates/cestory/src/lib.rs index 0fac3605..587d4789 100644 --- a/crates/cestory/src/lib.rs +++ b/crates/cestory/src/lib.rs @@ -834,7 +834,7 @@ async fn run_external_server( //FIXME: SHOULD BE DISABLE LOG KEY ON PRODUCTION !!! debug!( "Successfully load podr2 key public key is: {:?}", - &ceseal_props.podr2_key.pkey.to_pkcs1_der().unwrap().as_bytes() + hex::encode(&ceseal_props.podr2_key.pkey.to_pkcs1_der().unwrap().as_bytes()) ); let (ceseal_expert, expert_cmd_rx) = expert::CesealExpertStub::new(ceseal_props.clone()); diff --git a/crates/cestory/src/podr2.rs b/crates/cestory/src/podr2.rs index 03295b64..c69617ef 100644 --- a/crates/cestory/src/podr2.rs +++ b/crates/cestory/src/podr2.rs @@ -1,4 +1,7 @@ -use crate::{expert::CesealExpertStub, types::ThreadPoolSafeBox}; +use crate::{ + expert::{CesealExpertStub, ExternalResourceKind}, + types::ThreadPoolSafeBox, +}; use anyhow::{anyhow, Result}; use ces_crypto::sr25519::Signing; use ces_pdp::{HashSelf, Keys, QElement, Tag as PdpTag}; @@ -38,6 +41,7 @@ pub fn new_podr2_api_server(ceseal_expert: CesealExpertStub) -> Podr2ApiServer { threadpool: ceseal_expert.thread_pool(), block_num: 1024, ceseal_identity_key: ceseal_expert.identify_public_key().0, + ceseal_expert: ceseal_expert.clone(), }, ceseal_expert, }; @@ -66,6 +70,7 @@ pub struct Podr2Server { pub threadpool: ThreadPoolSafeBox, pub block_num: u64, pub ceseal_identity_key: [u8; 32], + pub ceseal_expert: CesealExpertStub, } pub struct Podr2VerifierServer { @@ -144,10 +149,25 @@ impl Podr2Api for Podr2Server { threadpool: self.threadpool.clone(), block_num: self.block_num.clone(), ceseal_identity_key: self.ceseal_identity_key.clone(), + ceseal_expert: self.ceseal_expert.clone(), }; //start receive tokio::spawn(async move { while let Some(result) = in_stream.next().await { + let _permit = match new_self + .ceseal_expert + .try_acquire_permit(ExternalResourceKind::Pord2Service) + .await + { + Ok(p) => p, + Err(err) => { + resp_tx + .send(Err(Status::internal(err.to_string()))) + .await + .expect("send failure of permit locking msg fail"); + return + }, + }; match result { Ok(v) => { if v.fragment_data.is_empty() && stream_rec_times == 0 { @@ -166,7 +186,7 @@ impl Podr2Api for Podr2Server { continue }; if !v.fragment_data.is_empty() && stream_rec_times == 1 { - match new_self.process_gen_tag_request(v) { + match new_self.process_gen_tag_request(v).await { Ok(response) => resp_tx .send(Ok(response)) .await @@ -354,7 +374,7 @@ fn convert_to_q_elements(qslices: Qslice) -> Result<(Vec, Challenge), } impl Podr2Server { - fn process_gen_tag_request<'life0>(&'life0 self, request: RequestGenTag) -> Result { + async fn process_gen_tag_request<'life0>(&'life0 self, request: RequestGenTag) -> Result { let now = Instant::now(); let mut h = Podr2Hash::new(); h.load_field(request.custom_data.as_bytes()); @@ -369,10 +389,6 @@ impl Podr2Server { .try_into() .map_err(|_| Status::invalid_argument("file_name hash bytes length should be 64".to_string()))?; - let pool = self - .threadpool - .lock() - .map_err(|e| Status::internal("lock global threadpool fail:".to_string() + &e.to_string()))?; //check fragement data is equal to fragement name let mut check_fragment_hash = Podr2Hash::new(); check_fragment_hash.load_field(&request.fragment_data); @@ -384,13 +400,6 @@ impl Podr2Server { &request.fragment_name, &fragment_data_hash_string ))) } - let tag = self - .podr2_keys - .sig_gen_with_data(request.fragment_data, self.block_num, &request.fragment_name, h, pool.clone()) - .map_err(|e| Status::internal(format!("AlgorithmError: {}", e.error_code.to_string())))?; - let u_sig = self.podr2_keys.sign_data(&calculate_hash(tag.t.u.as_bytes())).map_err(|e| { - Status::invalid_argument(format!("Failed to calculate u's signature {:?}", e.error_code.to_string())) - })?; let mut tag_sig_info_history = TagSigInfo { miner: AccountId32::from_slice(&request.miner_id[..]) @@ -440,6 +449,32 @@ impl Podr2Server { .sign_data(&calculate_hash(&tag_sig_info_history.encode())) .0 .to_vec(); + let new_self = Podr2Server { + podr2_keys: self.podr2_keys.clone(), + master_key: self.master_key.clone(), + threadpool: self.threadpool.clone(), + block_num: self.block_num.clone(), + ceseal_identity_key: self.ceseal_identity_key.clone(), + ceseal_expert: self.ceseal_expert.clone(), + }; + let tag = tokio::task::spawn_blocking(move || { + let pool = new_self + .threadpool + .lock() + .map_err(|e| Status::internal("lock global threadpool fail:".to_string() + &e.to_string()))?; + new_self + .podr2_keys + .sig_gen_with_data(request.fragment_data, new_self.block_num, &request.fragment_name, h, pool.clone()) + .map_err(|_| Status::invalid_argument("Algorithm error".to_string())) + }) + .await + .map_err(|_| Status::invalid_argument("Waiting for tag generate fail".to_string()))??; + + //compute u signature + let u_sig = self.podr2_keys.sign_data(&calculate_hash(tag.t.u.as_bytes())).map_err(|e| { + Status::invalid_argument(format!("Failed to calculate u's signature {:?}", e.error_code.to_string())) + })?; + info!("[🚀Generate tag] PoDR2 Sig Gen Completed in: {:.2?}. file name is {:?}", now.elapsed(), &tag.t.name); Ok(ResponseGenTag { processing: true, From fe94b97ccd92726c97381c07618b9d3169dcdb4c Mon Sep 17 00:00:00 2001 From: Demos Chiang <69138672+democ98@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:55:04 +0800 Subject: [PATCH 3/3] fix:get public key service not work as expect --- crates/cestory/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/cestory/src/lib.rs b/crates/cestory/src/lib.rs index 587d4789..14d771f6 100644 --- a/crates/cestory/src/lib.rs +++ b/crates/cestory/src/lib.rs @@ -873,15 +873,15 @@ async fn run_external_server( public_listener_addr, ceseal_props.role ); let mut server = Server::builder(); - server.add_service(pubkeys); let router = match ceseal_props.role { ces_types::WorkerRole::Full => server + .add_service(pubkeys) .add_service(podr2_srv) .add_service(podr2v_srv) .add_service(pois_srv) .add_service(poisv_srv), - ces_types::WorkerRole::Verifier => server.add_service(podr2v_srv).add_service(poisv_srv), - ces_types::WorkerRole::Marker => server.add_service(podr2_srv).add_service(pois_srv), + ces_types::WorkerRole::Verifier => server.add_service(pubkeys).add_service(podr2v_srv).add_service(poisv_srv), + ces_types::WorkerRole::Marker => server.add_service(pubkeys).add_service(podr2_srv).add_service(pois_srv), }; let result = router.serve(public_listener_addr).await; let _ = ext_srv_quit_tx.send(result);