use {
crate::{
dragonsmouth::{RESERVED_FILTER_NAME, block_accumulator::DragonsmouthBlockCumulator},
state_machine::{DeadBlockDetected, ForkDetected, SlotCommitmentStatusUpdate},
stream::{
Block, BlockEventStore, BlockMachineOutput, BlockStream, SimpleBlockStore,
SimpleBlockStoreIter,
},
},
futures_util::Stream,
solana_clock::Slot,
solana_commitment_config::CommitmentLevel,
std::task::ready,
tonic::async_trait,
yellowstone_grpc_client::{GeyserGrpcClient, GeyserGrpcClientError, GeyserStream},
yellowstone_grpc_proto::geyser::{
CommitmentLevel as ProtoCommitmentLevel, SubscribeRequest, SubscribeRequestFilterSlots,
SubscribeUpdate,
},
};
pub struct DragonsmouthBlockStream {
inner: BlockStream<GeyserStream, SubscribeUpdate, DragonsmouthBlockCumulator>,
}
pub struct DragonsmouthBlock {
inner: Block<SimpleBlockStore<SubscribeUpdate>>,
}
impl DragonsmouthBlock {
pub fn slot(&self) -> Slot {
self.inner.slot
}
}
impl From<Block<SimpleBlockStore<SubscribeUpdate>>> for DragonsmouthBlock {
fn from(block: Block<SimpleBlockStore<SubscribeUpdate>>) -> Self {
Self { inner: block }
}
}
impl BlockEventStore for DragonsmouthBlock {
type EventT = SubscribeUpdate;
type Iter<'a> = SimpleBlockStoreIter<'a, SubscribeUpdate>;
type IntoIter = std::vec::IntoIter<SubscribeUpdate>;
fn len(&self) -> usize {
self.inner.as_ref().len()
}
fn iter(&self) -> Self::Iter<'_> {
self.inner.as_ref().iter()
}
fn account_iter(&self) -> Self::Iter<'_> {
self.inner.as_ref().account_iter()
}
fn transaction_iter(&self) -> Self::Iter<'_> {
self.inner.as_ref().transaction_iter()
}
fn entry_iter(&self) -> Self::Iter<'_> {
self.inner.as_ref().entry_iter()
}
fn other_iter(&self) -> Self::Iter<'_> {
self.inner.as_ref().other_iter()
}
fn into_iter(self) -> Self::IntoIter {
self.inner.events.into_iter()
}
}
pub enum BlockStreamEvent {
FrozenBlock(DragonsmouthBlock),
SlotCommitmentUpdate(SlotCommitmentStatusUpdate),
ForkDetected(ForkDetected),
DeadBlockDetected(DeadBlockDetected),
}
impl Stream for DragonsmouthBlockStream {
type Item = Result<BlockStreamEvent, BlockMachineError>;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
let inner = unsafe { self.map_unchecked_mut(|s| &mut s.inner) };
let poll = ready!(inner.poll_next(cx));
match poll {
Some(Ok(output)) => {
let output2 = match output {
BlockMachineOutput::FrozenBlock(block) => {
let block2 = DragonsmouthBlock::from(block);
BlockStreamEvent::FrozenBlock(block2)
}
BlockMachineOutput::SlotCommitmentUpdate(slot_commitment_status_update) => {
BlockStreamEvent::SlotCommitmentUpdate(slot_commitment_status_update)
}
BlockMachineOutput::ForkDetected(fork_detected) => {
BlockStreamEvent::ForkDetected(fork_detected)
}
BlockMachineOutput::DeadBlockDetected(dead_block_detected) => {
BlockStreamEvent::DeadBlockDetected(dead_block_detected)
}
};
std::task::Poll::Ready(Some(Ok(output2)))
}
Some(Err(e)) => std::task::Poll::Ready(Some(Err(BlockMachineError::GrpcError(e)))),
None => std::task::Poll::Ready(None),
}
}
}
#[async_trait]
pub trait GeyserGrpcExt {
async fn subscribe_block(
&mut self,
subscribe_request: SubscribeRequest,
) -> Result<DragonsmouthBlockStream, GeyserGrpcClientError>;
}
pub const DEFAULT_SUBSCRIBE_BLOCK_CHANNEL_CAPACITY: usize = 1_000_000;
#[derive(Debug, thiserror::Error)]
pub enum BlockMachineError {
#[error(transparent)]
GrpcError(#[from] tonic::Status),
}
#[async_trait]
impl GeyserGrpcExt for GeyserGrpcClient {
async fn subscribe_block(
&mut self,
mut subscribe_request: SubscribeRequest,
) -> Result<DragonsmouthBlockStream, GeyserGrpcClientError> {
let proto_commitment_level =
ProtoCommitmentLevel::try_from(subscribe_request.commitment.unwrap_or(0))
.expect("Invalid commitment level in subscribe request");
assert!(
subscribe_request.blocks.is_empty(),
"custom `blocks` filter is not compatible with block machine"
);
assert!(
subscribe_request.slots.is_empty(),
"custom `slots` filter is not compatible with block machine"
);
let commitment_level = match proto_commitment_level {
ProtoCommitmentLevel::Processed => CommitmentLevel::Processed,
ProtoCommitmentLevel::Confirmed => CommitmentLevel::Confirmed,
ProtoCommitmentLevel::Finalized => CommitmentLevel::Finalized,
};
subscribe_request.slots.insert(
RESERVED_FILTER_NAME.to_owned(),
SubscribeRequestFilterSlots {
interslot_updates: Some(true),
..Default::default()
},
);
subscribe_request
.blocks_meta
.insert(RESERVED_FILTER_NAME.to_owned(), Default::default());
subscribe_request
.entry
.insert(RESERVED_FILTER_NAME.to_owned(), Default::default());
subscribe_request.commitment = Some(0);
let (_sink, source) = self.subscribe_with_request(Some(subscribe_request)).await?;
let block_stream = BlockStream::new(source, Default::default(), commitment_level);
let dragonsmouth_block_stream = DragonsmouthBlockStream {
inner: block_stream,
};
Ok(dragonsmouth_block_stream)
}
}