use futures::{channel::mpsc, StreamExt};
use log::error;
use solana_entry::entry::Entry;
use tokio::task::JoinHandle;
use crate::common::AnyResult;
use crate::protos::shredstream::{
shredstream_proxy_client::ShredstreamProxyClient, SubscribeEntriesRequest,
};
use crate::streaming::shred::TransactionWithSlot;
pub struct ShredStreamHandler;
impl ShredStreamHandler {
pub async fn start_stream_processing(
mut client: ShredstreamProxyClient<tonic::transport::Channel>,
channel_size: usize,
) -> AnyResult<(JoinHandle<()>, mpsc::Receiver<TransactionWithSlot>)> {
let request = tonic::Request::new(SubscribeEntriesRequest {});
let stream = client.subscribe_entries(request).await?.into_inner();
let (tx, rx) = mpsc::channel::<TransactionWithSlot>(channel_size);
let stream_task = tokio::spawn(Self::process_stream_messages(stream, tx));
Ok((stream_task, rx))
}
async fn process_stream_messages(
mut stream: tonic::codec::Streaming<crate::protos::shredstream::Entry>,
mut tx: mpsc::Sender<TransactionWithSlot>,
) {
while let Some(message) = stream.next().await {
match message {
Ok(msg) => {
if let Err(e) = Self::handle_stream_message(msg, &mut tx).await {
error!("Error handling stream message: {e:?}");
continue;
}
}
Err(error) => {
error!("Stream error: {error:?}");
break;
}
}
}
}
async fn handle_stream_message(
msg: crate::protos::shredstream::Entry,
tx: &mut mpsc::Sender<TransactionWithSlot>,
) -> AnyResult<()> {
if let Ok(entries) = bincode::deserialize::<Vec<Entry>>(&msg.entries) {
for entry in entries {
for transaction in entry.transactions {
let transaction_with_slot =
TransactionWithSlot::new(transaction.clone(), msg.slot);
if let Err(e) = tx.try_send(transaction_with_slot) {
if e.is_full() {
log::warn!("Transaction channel is full, dropping transaction");
} else {
return Err(e.into());
}
}
}
}
}
Ok(())
}
pub fn start_transaction_processing<F>(
mut rx: mpsc::Receiver<TransactionWithSlot>,
processor: F,
) -> JoinHandle<()>
where
F: Fn(TransactionWithSlot) -> Result<(), Box<dyn std::error::Error + Send + Sync>>
+ Send
+ Sync
+ 'static,
{
tokio::spawn(async move {
while let Some(transaction_with_slot) = rx.next().await {
if let Err(e) = processor(transaction_with_slot) {
error!("Error processing transaction: {e:?}");
}
}
})
}
}