Skip to content

Commit 8b910f1

Browse files
authored
Merge branch 'main' into aryan/v3-on-demand-pool-fetch
2 parents 2ab826c + e7e48be commit 8b910f1

2 files changed

Lines changed: 224 additions & 0 deletions

File tree

crates/solana-indexer/src/indexer/ingester.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,3 +401,6 @@ fn subscribe_request(
401401
..Default::default()
402402
}
403403
}
404+
405+
#[cfg(test)]
406+
mod tests;
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
use {
2+
super::{Error, INGEST_TO_DECODER_CAPACITY, Ingester},
3+
crate::types::{
4+
Signature,
5+
channel::StreamUpdate,
6+
slot::Slot,
7+
wire::{
8+
SubscribeUpdate,
9+
SubscribeUpdateAccount,
10+
SubscribeUpdateAccountInfo,
11+
SubscribeUpdatePing,
12+
SubscribeUpdateSlot,
13+
SubscribeUpdateTransaction,
14+
SubscribeUpdateTransactionInfo,
15+
UpdateOneof,
16+
},
17+
},
18+
futures::stream,
19+
std::sync::{
20+
Arc,
21+
atomic::{AtomicU64, Ordering},
22+
},
23+
tokio::sync::mpsc::channel,
24+
// Update variants the ingester ignores by falling through its match. Pulled
25+
// from the proto crate directly rather than the curated `wire` surface.
26+
yellowstone_grpc_proto::{
27+
geyser::{
28+
SubscribeUpdateBlock,
29+
SubscribeUpdateBlockMeta,
30+
SubscribeUpdateEntry,
31+
SubscribeUpdatePong,
32+
SubscribeUpdateTransactionStatus,
33+
},
34+
tonic::Status,
35+
},
36+
};
37+
38+
fn signature(n: u8) -> Signature {
39+
Signature::from([n; 64])
40+
}
41+
42+
fn signature_bytes(n: u8) -> Vec<u8> {
43+
signature(n).as_ref().to_vec()
44+
}
45+
46+
fn tx_update(slot: u64, sig: u8) -> Result<SubscribeUpdate, Status> {
47+
Ok(SubscribeUpdate {
48+
update_oneof: Some(UpdateOneof::Transaction(SubscribeUpdateTransaction {
49+
slot,
50+
transaction: Some(SubscribeUpdateTransactionInfo {
51+
signature: signature_bytes(sig),
52+
..Default::default()
53+
}),
54+
})),
55+
..Default::default()
56+
})
57+
}
58+
59+
fn account_update(slot: u64, sig: u8) -> Result<SubscribeUpdate, Status> {
60+
Ok(SubscribeUpdate {
61+
update_oneof: Some(UpdateOneof::Account(SubscribeUpdateAccount {
62+
slot,
63+
account: Some(SubscribeUpdateAccountInfo {
64+
txn_signature: Some(signature_bytes(sig)),
65+
..Default::default()
66+
}),
67+
..Default::default()
68+
})),
69+
..Default::default()
70+
})
71+
}
72+
73+
fn slot_update(slot: u64) -> Result<SubscribeUpdate, Status> {
74+
Ok(SubscribeUpdate {
75+
update_oneof: Some(UpdateOneof::Slot(SubscribeUpdateSlot {
76+
slot,
77+
..Default::default()
78+
})),
79+
..Default::default()
80+
})
81+
}
82+
83+
fn update_of(update: UpdateOneof) -> Result<SubscribeUpdate, Status> {
84+
Ok(SubscribeUpdate {
85+
update_oneof: Some(update),
86+
..Default::default()
87+
})
88+
}
89+
90+
fn ingester(
91+
stream: impl stream::Stream<Item = Result<SubscribeUpdate, Status>> + Unpin + Send,
92+
) -> (
93+
Ingester<impl stream::Stream<Item = Result<SubscribeUpdate, Status>> + Unpin + Send>,
94+
tokio::sync::mpsc::Receiver<StreamUpdate>,
95+
Arc<AtomicU64>,
96+
) {
97+
let (tx, rx) = channel(INGEST_TO_DECODER_CAPACITY);
98+
let latest_chain_slot = Arc::new(AtomicU64::new(0));
99+
(
100+
Ingester::new(stream, tx, latest_chain_slot.clone()),
101+
rx,
102+
latest_chain_slot,
103+
)
104+
}
105+
106+
#[tokio::test]
107+
async fn transaction_update_with_valid_signature_is_forwarded() {
108+
let signature = signature(1);
109+
let (mut ingester, mut rx, _) = ingester(stream::iter([tx_update(42, 1)]));
110+
111+
assert!(matches!(ingester.run().await, Err(Error::StreamEnded)));
112+
let update = rx.recv().await.unwrap();
113+
assert!(
114+
matches!(update, StreamUpdate::Tx { slot: Slot(42), signature: s, .. } if s == signature)
115+
);
116+
assert!(rx.is_empty());
117+
}
118+
119+
#[tokio::test]
120+
async fn account_update_with_body_is_forwarded() {
121+
let signature = signature(2);
122+
let (mut ingester, mut rx, _) = ingester(stream::iter([account_update(100, 2)]));
123+
124+
assert!(matches!(ingester.run().await, Err(Error::StreamEnded)));
125+
let update = rx.recv().await.unwrap();
126+
assert!(
127+
matches!(update, StreamUpdate::Account { slot: Slot(100), txn_signature: Some(s), .. } if s == signature)
128+
);
129+
assert!(rx.is_empty());
130+
}
131+
132+
#[tokio::test]
133+
async fn slot_update_advances_latest_chain_slot() {
134+
let (mut ingester, _rx, slot) = ingester(stream::iter([slot_update(9_001)]));
135+
136+
assert!(matches!(ingester.run().await, Err(Error::StreamEnded)));
137+
assert_eq!(slot.load(Ordering::Relaxed), 9_001);
138+
}
139+
140+
#[tokio::test]
141+
async fn unrelated_and_empty_updates_are_ignored() {
142+
let (mut ingester, mut rx, slot) = ingester(stream::iter([
143+
Ok(SubscribeUpdate::default()),
144+
update_of(UpdateOneof::Ping(SubscribeUpdatePing::default())),
145+
update_of(UpdateOneof::Pong(SubscribeUpdatePong::default())),
146+
update_of(UpdateOneof::TransactionStatus(
147+
SubscribeUpdateTransactionStatus::default(),
148+
)),
149+
update_of(UpdateOneof::Block(SubscribeUpdateBlock::default())),
150+
update_of(UpdateOneof::BlockMeta(SubscribeUpdateBlockMeta::default())),
151+
update_of(UpdateOneof::Entry(SubscribeUpdateEntry::default())),
152+
tx_update(7, 3),
153+
]));
154+
155+
assert!(matches!(ingester.run().await, Err(Error::StreamEnded)));
156+
let update = rx.recv().await.unwrap();
157+
assert!(
158+
matches!(update, StreamUpdate::Tx { slot: Slot(7), signature: s, .. } if s == signature(3))
159+
);
160+
assert!(rx.is_empty());
161+
assert_eq!(slot.load(Ordering::Relaxed), 0);
162+
}
163+
164+
#[tokio::test]
165+
async fn transaction_without_body_or_malformed_signature_is_skipped() {
166+
let signature = signature(4);
167+
let (mut ingester, mut rx, _) = ingester(stream::iter([
168+
Ok(SubscribeUpdate {
169+
update_oneof: Some(UpdateOneof::Transaction(SubscribeUpdateTransaction {
170+
slot: 1,
171+
transaction: None,
172+
})),
173+
..Default::default()
174+
}),
175+
Ok(SubscribeUpdate {
176+
update_oneof: Some(UpdateOneof::Transaction(SubscribeUpdateTransaction {
177+
slot: 2,
178+
transaction: Some(SubscribeUpdateTransactionInfo {
179+
signature: vec![1, 2, 3],
180+
..Default::default()
181+
}),
182+
})),
183+
..Default::default()
184+
}),
185+
tx_update(3, 4),
186+
]));
187+
188+
assert!(matches!(ingester.run().await, Err(Error::StreamEnded)));
189+
let update = rx.recv().await.unwrap();
190+
assert!(
191+
matches!(update, StreamUpdate::Tx { slot: Slot(3), signature: s, .. } if s == signature)
192+
);
193+
assert!(rx.is_empty());
194+
}
195+
196+
#[tokio::test]
197+
async fn terminal_grpc_status_returns_stream_error() {
198+
let status = Status::invalid_argument("boom");
199+
let (mut ingester, _rx, _) = ingester(stream::iter([Err(status.clone())]));
200+
201+
let result = ingester.run().await;
202+
assert!(
203+
matches!(result, Err(Error::Stream(s)) if s.code() == status.code() && s.message() == status.message())
204+
);
205+
}
206+
207+
#[tokio::test]
208+
async fn clean_stream_end_returns_stream_ended() {
209+
let (mut ingester, _rx, _) =
210+
ingester(stream::iter(Vec::<Result<SubscribeUpdate, Status>>::new()));
211+
212+
assert!(matches!(ingester.run().await, Err(Error::StreamEnded)));
213+
}
214+
215+
#[tokio::test]
216+
async fn closed_decoder_receiver_stops_cleanly() {
217+
let (mut ingester, rx, _) = ingester(stream::iter([tx_update(1, 5)]));
218+
drop(rx);
219+
220+
assert!(ingester.run().await.is_ok());
221+
}

0 commit comments

Comments
 (0)