use axum::extract::ws::WebSocket;
pub use entropy_protocol::protocol_transport::SubscribeMessage;
use entropy_protocol::{
protocol_transport::{
errors::WsError,
noise::{noise_handshake_initiator, noise_handshake_responder},
ws_to_channels, WsChannels,
},
PartyId, ValidatorInfo,
};
use entropy_shared::X25519PublicKey;
use futures::future;
use sp_core::{sr25519, Pair};
use subxt::utils::AccountId32;
use tokio_tungstenite::connect_async;
use super::ProtocolErr;
use crate::{
signing_client::{SessionId, SubscribeErr},
AppState, ListenerState, SUBSCRIBE_TIMEOUT_SECONDS,
};
#[tracing::instrument(
skip_all,
fields(validator_info, session_uid, signer)
level = tracing::Level::DEBUG
)]
pub async fn open_protocol_connections(
validators_info: &[ValidatorInfo],
session_id: &SessionId,
signer: &sr25519::Pair,
state: &ListenerState,
x25519_secret_key: &x25519_dalek::StaticSecret,
) -> Result<(), ProtocolErr> {
tracing::debug!("Opening protocol connections");
let connect_to_validators = validators_info
.iter()
.filter(|validators_info| {
let initiate_connection = signer.public().0 > validators_info.tss_account.0;
if !initiate_connection {
tracing::debug!(
"Waiting for {:?} to open a connection with us.",
validators_info.tss_account.0
);
}
initiate_connection
})
.map(|validator_info| async move {
tracing::debug!(
"Attempting to open protocol connections with {:?}",
validator_info.tss_account.0.clone()
);
let ws_endpoint = format!("ws://{}/v1/ws", validator_info.ip_address);
let (ws_stream, _response) =
connect_async(ws_endpoint).await.map_err(|e| ProtocolErr::ConnectionError {
source: e,
account_id: validator_info.tss_account.clone(),
})?;
let subscribe_message_vec =
bincode::serialize(&SubscribeMessage::new(session_id.clone(), signer)?)?;
let mut encrypted_connection = noise_handshake_initiator(
ws_stream,
x25519_secret_key,
validator_info.x25519_public_key,
subscribe_message_vec,
)
.await
.map_err(|e| ProtocolErr::EncryptedConnection {
source: e,
account_id: validator_info.tss_account.clone(),
})?;
let response_message = encrypted_connection.recv().await.map_err(|e| {
ProtocolErr::EncryptedConnection {
source: e,
account_id: validator_info.tss_account.clone(),
}
})?;
let subscribe_response: Result<(), String> = bincode::deserialize(&response_message)?;
if let Err(error_message) = subscribe_response {
return Err(ProtocolErr::BadSubscribeMessage {
message: error_message,
account_id: validator_info.tss_account.clone(),
});
}
let ws_channels = get_ws_channels(state, session_id, &validator_info.tss_account)
.map_err(|e| ProtocolErr::Subscribe {
source: e,
account_id: validator_info.tss_account.clone(),
})?;
let remote_party_id = PartyId::new(validator_info.tss_account.clone());
let account_id = validator_info.tss_account.clone();
tokio::spawn(async move {
ws_to_channels(encrypted_connection, ws_channels, remote_party_id).await.map_err(
|err| {
tracing::warn!("{:?}", err);
Err::<(), ProtocolErr>(ProtocolErr::EncryptedConnection {
source: err.into(),
account_id,
})
},
)
});
Ok::<_, ProtocolErr>(())
})
.collect::<Vec<_>>();
future::try_join_all(connect_to_validators).await?;
Ok(())
}
pub async fn handle_socket(socket: WebSocket, app_state: AppState) -> Result<(), WsError> {
if !app_state.cache.is_ready() {
return Err(WsError::NotReady);
}
let (mut encrypted_connection, serialized_signed_message) =
noise_handshake_responder(socket, &app_state.x25519_secret)
.await
.map_err(|e| WsError::EncryptedConnection(e.to_string()))?;
let remote_public_key = encrypted_connection
.remote_public_key()
.map_err(|e| WsError::EncryptedConnection(e.to_string()))?;
let (subscribe_response, ws_channels_option) = match handle_initial_incoming_ws_message(
serialized_signed_message,
remote_public_key,
app_state,
)
.await
{
Ok((ws_channels, party_id)) => (Ok(()), Some((ws_channels, party_id))),
Err(err) => (Err(format!("{err:?}")), None),
};
let subscribe_response_vec = bincode::serialize(&subscribe_response)?;
encrypted_connection
.send(subscribe_response_vec)
.await
.map_err(|e| WsError::EncryptedConnection(e.to_string()))?;
let (ws_channels, remote_party_id) = ws_channels_option.ok_or(WsError::BadSubscribeMessage)?;
ws_to_channels(encrypted_connection, ws_channels, remote_party_id).await?;
Ok(())
}
async fn handle_initial_incoming_ws_message(
serialized_subscribe_message: Vec<u8>,
remote_public_key: X25519PublicKey,
app_state: AppState,
) -> Result<(WsChannels, PartyId), SubscribeErr> {
let msg: SubscribeMessage = bincode::deserialize(&serialized_subscribe_message)?;
tracing::info!("Got ws connection, with message: {msg:?}");
msg.check_supported()?;
if !msg.verify()? {
return Err(SubscribeErr::InvalidSignature("Invalid signature."));
}
if !app_state.cache.listener_state.contains_listener(&msg.session_id)? {
tracing::warn!("Cannot find associated listener - waiting");
tokio::time::sleep(std::time::Duration::from_secs(SUBSCRIBE_TIMEOUT_SECONDS)).await;
};
{
let mut listeners = app_state
.cache
.listener_state
.listeners
.lock()
.map_err(|e| SubscribeErr::LockError(e.to_string()))?;
let listener =
listeners.get(&msg.session_id).ok_or(SubscribeErr::NoListener("no listener"))?;
if !listener.validators.iter().any(|(validator_account_id, validator_x25519_pk)| {
validator_account_id == &msg.account_id().0 && validator_x25519_pk == &remote_public_key
}) {
listeners.remove(&msg.session_id);
return Err(SubscribeErr::Decryption(
"Public key does not match any of those expected for this protocol session"
.to_string(),
));
}
}
let ws_channels =
get_ws_channels(&app_state.cache.listener_state, &msg.session_id, &msg.account_id())?;
Ok((ws_channels, PartyId::new(msg.account_id())))
}
fn get_ws_channels(
state: &ListenerState,
session_id: &SessionId,
tss_account: &AccountId32,
) -> Result<WsChannels, SubscribeErr> {
let mut listeners =
state.listeners.lock().map_err(|e| SubscribeErr::LockError(e.to_string()))?;
let listener = listeners
.get_mut(session_id)
.ok_or(SubscribeErr::NoListener("No listener when getting ws channels"))?;
let ws_channels = listener.subscribe(tss_account)?;
if ws_channels.is_final {
let listener =
listeners.remove(session_id).ok_or(SubscribeErr::NoListener("listener remove"))?;
let (tx, broadcaster) = listener.into_broadcaster();
let _ = tx.send(Ok(broadcaster));
};
Ok(ws_channels)
}