Geyser gRPC - कोड उदाहरण
Geyser gRPC के आम use cases के लिए व्यावहारिक Rust उदाहरण। अपना Geyser gRPC endpoint भरें। Solana Stream SDK, Geyser gRPC को support करता है।
बुनियादी subscription: सभी non-vote, non-failed transactions
यह जानबूझकर एक व्यापक transaction subscription है। Failed transactions को हटाने से traffic घटता है, लेकिन बची हुई stream फिर भी काफ़ी बड़ी हो सकती है। अपने consumer को वास्तव में जिस काम की ज़रूरत है, उससे मेल खाने वाले account, signature या transaction filters जोड़ें, और processing को receive loop से बाहर रखें।
rust
use anyhow::Result;
use futures::{sink::SinkExt, stream::StreamExt};
use solana_signature::Signature;
use std::collections::HashMap;
use solana_stream_sdk::{
GeyserCommitmentLevel as CommitmentLevel, GeyserGrpcClient,
GeyserSubscribeRequest as SubscribeRequest,
GeyserSubscribeRequestFilterTransactions as SubscribeRequestFilterTransactions,
GeyserUpdateOneof as UpdateOneof,
};
#[tokio::main]
async fn main() -> Result<()> {
let endpoint = "http://your-erpc-grpc-endpoint"; // Set your ERPC gRPC URL
let mut client = GeyserGrpcClient::build_from_shared(endpoint)?
.connect()
.await?;
let (mut tx, mut stream) = client.subscribe().await?;
tx.send(SubscribeRequest {
transactions: HashMap::from([(
"all_transactions".to_string(),
SubscribeRequestFilterTransactions {
vote: Some(false), // Drop vote txs
failed: Some(false), // Drop failed txs
..Default::default()
},
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
})
.await?;
while let Some(Ok(msg)) = stream.next().await {
if let Some(UpdateOneof::Transaction(tx)) = msg.update_oneof {
let sig = tx
.transaction
.and_then(|t| Signature::try_from(t.signature.as_slice()).ok())
.unwrap_or_default();
println!("Slot: {} | Signature: {}", tx.slot, sig);
}
}
Ok(())
}use anyhow::Result;
use futures::{sink::SinkExt, stream::StreamExt};
use solana_signature::Signature;
use std::collections::HashMap;
use solana_stream_sdk::{
GeyserCommitmentLevel as CommitmentLevel, GeyserGrpcClient,
GeyserSubscribeRequest as SubscribeRequest,
GeyserSubscribeRequestFilterTransactions as SubscribeRequestFilterTransactions,
GeyserUpdateOneof as UpdateOneof,
};
#[tokio::main]
async fn main() -> Result<()> {
let endpoint = "http://your-erpc-grpc-endpoint"; // Set your ERPC gRPC URL
let mut client = GeyserGrpcClient::build_from_shared(endpoint)?
.connect()
.await?;
let (mut tx, mut stream) = client.subscribe().await?;
tx.send(SubscribeRequest {
transactions: HashMap::from([(
"all_transactions".to_string(),
SubscribeRequestFilterTransactions {
vote: Some(false), // Drop vote txs
failed: Some(false), // Drop failed txs
..Default::default()
},
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
})
.await?;
while let Some(Ok(msg)) = stream.next().await {
if let Some(UpdateOneof::Transaction(tx)) = msg.update_oneof {
let sig = tx
.transaction
.and_then(|t| Signature::try_from(t.signature.as_slice()).ok())
.unwrap_or_default();
println!("Slot: {} | Signature: {}", tx.slot, sig);
}
}
Ok(())
}Clean closure के बाद सीमित reconnect, gap recovery और ping/pong वाला durable client
rust
use anyhow::Result;
use futures::stream::StreamExt;
use std::{collections::HashMap, time::Duration};
use solana_stream_sdk::{
GeyserCommitmentLevel as CommitmentLevel, GeyserGrpcClient,
GeyserSlotStatus as SlotStatus, GeyserSubscribeRequest as SubscribeRequest,
GeyserSubscribeRequestFilterSlots as SubscribeRequestFilterSlots,
GeyserUpdateOneof as UpdateOneof,
};
#[tokio::main]
async fn main() -> Result<()> {
let endpoint = "http://your-erpc-grpc-endpoint";
let mut subscribe_request = SubscribeRequest {
slots: HashMap::from([(
"slots".to_string(),
SubscribeRequestFilterSlots::default(),
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
};
let mut tracked_slot: u64 = 0;
let mut retry_delay = Duration::from_secs(1);
let max_retry_delay = Duration::from_secs(30);
let mut first_diagnostic: Option<String> = None;
loop {
let mut client = match GeyserGrpcClient::build_from_shared(endpoint)?.connect().await {
Ok(client) => client,
Err(error) => {
let current_diagnostic = error.to_string();
first_diagnostic.get_or_insert_with(|| current_diagnostic.clone());
eprintln!("Connection error: {current_diagnostic}");
tokio::time::sleep(retry_delay).await;
retry_delay = (retry_delay * 2).min(max_retry_delay);
continue;
}
};
subscribe_request.from_slot = if tracked_slot > 0 {
Some(tracked_slot)
} else {
None
};
let (mut _sink, mut stream) = match client
.subscribe_with_request(Some(subscribe_request.clone()))
.await
{
Ok(subscription) => subscription,
Err(error) => {
let current_diagnostic = error.to_string();
first_diagnostic.get_or_insert_with(|| current_diagnostic.clone());
eprintln!("Subscribe error: {current_diagnostic}");
eprintln!("First diagnostic retained: {}", first_diagnostic.as_deref().unwrap());
return Err(error.into());
}
};
loop {
match stream.next().await {
Some(Ok(msg)) => {
retry_delay = Duration::from_secs(1);
if let Some(UpdateOneof::Slot(slot)) = msg.update_oneof {
tracked_slot = slot.slot;
println!(
"Slot: {} | Status: {:?}",
slot.slot,
SlotStatus::try_from(slot.status).unwrap()
);
}
}
Some(Err(error)) => {
let current_diagnostic = error.to_string();
first_diagnostic.get_or_insert_with(|| current_diagnostic.clone());
eprintln!("Stream error: {current_diagnostic}");
eprintln!("First diagnostic retained: {}", first_diagnostic.as_deref().unwrap());
return Err(error.into());
}
None => break,
}
}
tokio::time::sleep(retry_delay).await;
retry_delay = (retry_delay * 2).min(max_retry_delay);
}
}use anyhow::Result;
use futures::stream::StreamExt;
use std::{collections::HashMap, time::Duration};
use solana_stream_sdk::{
GeyserCommitmentLevel as CommitmentLevel, GeyserGrpcClient,
GeyserSlotStatus as SlotStatus, GeyserSubscribeRequest as SubscribeRequest,
GeyserSubscribeRequestFilterSlots as SubscribeRequestFilterSlots,
GeyserUpdateOneof as UpdateOneof,
};
#[tokio::main]
async fn main() -> Result<()> {
let endpoint = "http://your-erpc-grpc-endpoint";
let mut subscribe_request = SubscribeRequest {
slots: HashMap::from([(
"slots".to_string(),
SubscribeRequestFilterSlots::default(),
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
};
let mut tracked_slot: u64 = 0;
let mut retry_delay = Duration::from_secs(1);
let max_retry_delay = Duration::from_secs(30);
let mut first_diagnostic: Option<String> = None;
loop {
let mut client = match GeyserGrpcClient::build_from_shared(endpoint)?.connect().await {
Ok(client) => client,
Err(error) => {
let current_diagnostic = error.to_string();
first_diagnostic.get_or_insert_with(|| current_diagnostic.clone());
eprintln!("Connection error: {current_diagnostic}");
tokio::time::sleep(retry_delay).await;
retry_delay = (retry_delay * 2).min(max_retry_delay);
continue;
}
};
subscribe_request.from_slot = if tracked_slot > 0 {
Some(tracked_slot)
} else {
None
};
let (mut _sink, mut stream) = match client
.subscribe_with_request(Some(subscribe_request.clone()))
.await
{
Ok(subscription) => subscription,
Err(error) => {
let current_diagnostic = error.to_string();
first_diagnostic.get_or_insert_with(|| current_diagnostic.clone());
eprintln!("Subscribe error: {current_diagnostic}");
eprintln!("First diagnostic retained: {}", first_diagnostic.as_deref().unwrap());
return Err(error.into());
}
};
loop {
match stream.next().await {
Some(Ok(msg)) => {
retry_delay = Duration::from_secs(1);
if let Some(UpdateOneof::Slot(slot)) = msg.update_oneof {
tracked_slot = slot.slot;
println!(
"Slot: {} | Status: {:?}",
slot.slot,
SlotStatus::try_from(slot.status).unwrap()
);
}
}
Some(Err(error)) => {
let current_diagnostic = error.to_string();
first_diagnostic.get_or_insert_with(|| current_diagnostic.clone());
eprintln!("Stream error: {current_diagnostic}");
eprintln!("First diagnostic retained: {}", first_diagnostic.as_deref().unwrap());
return Err(error.into());
}
None => break,
}
}
tokio::time::sleep(retry_delay).await;
retry_delay = (retry_delay * 2).min(max_retry_delay);
}
}यह सावधानी वाला उदाहरण केवल connection स्थापित होने की विफलताओं और stream के साफ़ तरीके से बंद होने पर ही retry करता है। gRPC subscribe या stream error आने पर यह मौजूदा diagnostic को log करता है, पहले diagnostic को सँभालकर रखता है और फिर रुक जाता है। मैन्युअल रूप से retry करने से पहले, दिखाया गया ERPC सुधार और server की कोई भी retry जानकारी जाँच लें। Production code जानी-पहचानी अस्थायी विफलताओं को स्पष्ट रूप से retry कर सकता है, लेकिन उसे server के pause का पालन करना होगा और अस्वीकार हुए request को बिना बदले बार-बार नहीं भेजना चाहिए।
किसी mint के accounts को stream करें (token accounts)
rust
SubscribeRequest {
accounts: HashMap::from([(
"token_accounts_by_mint".to_string(),
SubscribeRequestFilterAccounts {
owner: vec!["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string()],
filters: vec![
SubscribeRequestFilterAccountsFilter {
filter: Some(Filter::TokenAccountState(true)),
},
SubscribeRequestFilterAccountsFilter {
filter: Some(Filter::Memcmp(SubscribeRequestFilterAccountsFilterMemcmp {
offset: 0,
data: Some(Data::Base58(
"pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn".to_string(), // replace with mint
)),
})),
},
],
..Default::default()
},
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
}SubscribeRequest {
accounts: HashMap::from([(
"token_accounts_by_mint".to_string(),
SubscribeRequestFilterAccounts {
owner: vec!["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string()],
filters: vec![
SubscribeRequestFilterAccountsFilter {
filter: Some(Filter::TokenAccountState(true)),
},
SubscribeRequestFilterAccountsFilter {
filter: Some(Filter::Memcmp(SubscribeRequestFilterAccountsFilterMemcmp {
offset: 0,
data: Some(Data::Base58(
"pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn".to_string(), // replace with mint
)),
})),
},
],
..Default::default()
},
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
}Transactions को stream करें (सफल, non-vote)
rust
SubscribeRequest {
transactions: HashMap::from([(
"all_transactions".to_string(),
SubscribeRequestFilterTransactions {
vote: Some(false),
failed: Some(false),
..Default::default()
},
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
}SubscribeRequest {
transactions: HashMap::from([(
"all_transactions".to_string(),
SubscribeRequestFilterTransactions {
vote: Some(false),
failed: Some(false),
..Default::default()
},
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
}Production-grade client की मुख्य बातें
- Reconnection और gap recovery (
from_slot) वाला durable client - Stream को चालू रखने के लिए ping/pong handling
- Ingress और processing के बीच bounded channels/backpressure
- उसी bidi stream पर dynamic subscription updates
- धीमे consumers/drop हुए updates के लिए error handling, metrics और logging
संयुक्त client pattern (gap recovery, ping/pong, dynamic subscriptions)
यह composition bounded ingress और dynamic subscriptions दिखाता है। इसका ingress task केवल connection स्थापित होने की विफलता या stream के साफ़ तरीके से खत्म होने के बाद प्रतीक्षा करके reconnect करता है। gRPC subscription या stream error रिपोर्ट किया जाता है और task रुक जाता है, ताकि उसका सुधार और retry जानकारी जाँची जा सके; यह कोई सामान्य retry policy नहीं है।
rust
use futures::{SinkExt, stream::StreamExt};
use solana_signature::Signature;
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::{RwLock, mpsc};
use solana_stream_sdk::{
GeyserCommitmentLevel as CommitmentLevel, GeyserGrpcClient, GeyserSlotStatus,
GeyserSubscribeRequest as SubscribeRequest,
GeyserSubscribeRequestFilterSlots as SubscribeRequestFilterSlots,
GeyserSubscribeRequestFilterTransactions as SubscribeRequestFilterTransactions,
GeyserSubscribeUpdate as SubscribeUpdate, GeyserUpdateOneof as UpdateOneof,
};
use solana_stream_sdk::yellowstone_grpc_proto::geyser::SubscribeRequestPing;
const UPDATE_CHANNEL_CAPACITY: usize = 10_000;
#[tokio::main]
async fn main() {
let endpoint = "http://your-erpc-grpc-endpoint".to_string();
let subscribe_request = Arc::new(RwLock::new(SubscribeRequest {
slots: HashMap::from([(
"slots".to_string(),
SubscribeRequestFilterSlots {
..Default::default()
},
)]),
transactions: HashMap::from([(
"all_transactions".to_string(),
SubscribeRequestFilterTransactions {
vote: Some(false),
failed: Some(false),
..Default::default()
},
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
}));
let (updates_tx, updates_rx) = mpsc::channel::<SubscribeUpdate>(UPDATE_CHANNEL_CAPACITY);
let (subscribe_tx, subscribe_rx) = mpsc::unbounded_channel::<SubscribeRequest>();
let dispatcher_handle = tokio::spawn(async move {
dispatcher_loop(updates_rx).await;
});
let ingress_handle = tokio::spawn(async move {
ingress_loop(
updates_tx,
subscribe_rx,
endpoint,
subscribe_request,
)
.await;
});
if let Err(e) = tokio::join!(dispatcher_handle, ingress_handle).0 {
println!("Error: {:?}", e);
}
}
async fn ingress_loop(
updates_tx: mpsc::Sender<SubscribeUpdate>,
mut subscribe_rx: mpsc::UnboundedReceiver<SubscribeRequest>,
endpoint: String,
subscribe_request: Arc<RwLock<SubscribeRequest>>,
) {
let mut tracked_slot: u64 = 0;
let mut retry_delay = Duration::from_secs(1);
let max_retry_delay = Duration::from_secs(30);
loop {
let builder = match GeyserGrpcClient::build_from_shared(endpoint.clone()) {
Ok(builder) => builder,
Err(error) => {
eprintln!("Client configuration error: {error}");
return;
}
};
let mut client = match builder.connect().await {
Ok(client) => client,
Err(error) => {
eprintln!("Connection error: {error}");
tokio::time::sleep(retry_delay).await;
retry_delay = (retry_delay * 2).min(max_retry_delay);
continue;
}
};
subscribe_request.write().await.from_slot = if tracked_slot > 0 {
Some(tracked_slot)
} else {
None
};
match client
.subscribe_with_request(Some(subscribe_request.read().await.clone()))
.await
{
Ok((mut subscribe_tx, mut stream)) => {
loop {
tokio::select! {
subscription = subscribe_rx.recv() => {
match subscription {
Some(subscribe_request) => {
if let Err(error) = subscribe_tx.send(subscribe_request).await {
eprintln!("Subscription update error: {error}");
return;
}
}
None => return,
}
}
result = stream.next() => {
match result {
Some(Ok(update)) => {
retry_delay = Duration::from_secs(1);
if matches!(update.update_oneof, Some(UpdateOneof::Ping(_))) {
if let Err(error) = subscribe_tx
.send(SubscribeRequest {
ping: Some(SubscribeRequestPing { id: 1 }),
..Default::default()
})
.await
{
eprintln!("Ping response error: {error}");
return;
}
}
if matches!(update.update_oneof, Some(UpdateOneof::Ping(_)) | Some(UpdateOneof::Pong(_))) {
continue;
}
if let Some(UpdateOneof::Slot(ref slot_update)) = update.update_oneof {
tracked_slot = slot_update.slot;
continue;
}
if let Err(error) = updates_tx.send(update.clone()).await {
eprintln!("Update consumer stopped: {error}");
return;
}
}
Some(Err(error)) => {
eprintln!("Stream error: {error}");
return;
}
None => {
println!("Stream ended cleanly");
break;
}
}
}
}
}
}
Err(error) => {
eprintln!("Subscribe error: {error}");
return;
}
};
println!("Disconnected; retrying after bounded backoff...");
tokio::time::sleep(retry_delay).await;
retry_delay = (retry_delay * 2).min(max_retry_delay);
}
}
async fn dispatcher_loop(mut updates_rx: mpsc::Receiver<SubscribeUpdate>) {
loop {
if let Some(update) = updates_rx.recv().await {
match update.update_oneof {
Some(UpdateOneof::Transaction(tx)) => {
let sig = tx
.transaction
.and_then(|t| Signature::try_from(t.signature.as_slice()).ok())
.unwrap_or_default();
println!("Slot: {} | Signature: {}", tx.slot, sig);
}
_ => {
println!("Received update: {:?}", update);
}
}
}
}
}use futures::{SinkExt, stream::StreamExt};
use solana_signature::Signature;
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::{RwLock, mpsc};
use solana_stream_sdk::{
GeyserCommitmentLevel as CommitmentLevel, GeyserGrpcClient, GeyserSlotStatus,
GeyserSubscribeRequest as SubscribeRequest,
GeyserSubscribeRequestFilterSlots as SubscribeRequestFilterSlots,
GeyserSubscribeRequestFilterTransactions as SubscribeRequestFilterTransactions,
GeyserSubscribeUpdate as SubscribeUpdate, GeyserUpdateOneof as UpdateOneof,
};
use solana_stream_sdk::yellowstone_grpc_proto::geyser::SubscribeRequestPing;
const UPDATE_CHANNEL_CAPACITY: usize = 10_000;
#[tokio::main]
async fn main() {
let endpoint = "http://your-erpc-grpc-endpoint".to_string();
let subscribe_request = Arc::new(RwLock::new(SubscribeRequest {
slots: HashMap::from([(
"slots".to_string(),
SubscribeRequestFilterSlots {
..Default::default()
},
)]),
transactions: HashMap::from([(
"all_transactions".to_string(),
SubscribeRequestFilterTransactions {
vote: Some(false),
failed: Some(false),
..Default::default()
},
)]),
commitment: Some(CommitmentLevel::Confirmed as i32),
..Default::default()
}));
let (updates_tx, updates_rx) = mpsc::channel::<SubscribeUpdate>(UPDATE_CHANNEL_CAPACITY);
let (subscribe_tx, subscribe_rx) = mpsc::unbounded_channel::<SubscribeRequest>();
let dispatcher_handle = tokio::spawn(async move {
dispatcher_loop(updates_rx).await;
});
let ingress_handle = tokio::spawn(async move {
ingress_loop(
updates_tx,
subscribe_rx,
endpoint,
subscribe_request,
)
.await;
});
if let Err(e) = tokio::join!(dispatcher_handle, ingress_handle).0 {
println!("Error: {:?}", e);
}
}
async fn ingress_loop(
updates_tx: mpsc::Sender<SubscribeUpdate>,
mut subscribe_rx: mpsc::UnboundedReceiver<SubscribeRequest>,
endpoint: String,
subscribe_request: Arc<RwLock<SubscribeRequest>>,
) {
let mut tracked_slot: u64 = 0;
let mut retry_delay = Duration::from_secs(1);
let max_retry_delay = Duration::from_secs(30);
loop {
let builder = match GeyserGrpcClient::build_from_shared(endpoint.clone()) {
Ok(builder) => builder,
Err(error) => {
eprintln!("Client configuration error: {error}");
return;
}
};
let mut client = match builder.connect().await {
Ok(client) => client,
Err(error) => {
eprintln!("Connection error: {error}");
tokio::time::sleep(retry_delay).await;
retry_delay = (retry_delay * 2).min(max_retry_delay);
continue;
}
};
subscribe_request.write().await.from_slot = if tracked_slot > 0 {
Some(tracked_slot)
} else {
None
};
match client
.subscribe_with_request(Some(subscribe_request.read().await.clone()))
.await
{
Ok((mut subscribe_tx, mut stream)) => {
loop {
tokio::select! {
subscription = subscribe_rx.recv() => {
match subscription {
Some(subscribe_request) => {
if let Err(error) = subscribe_tx.send(subscribe_request).await {
eprintln!("Subscription update error: {error}");
return;
}
}
None => return,
}
}
result = stream.next() => {
match result {
Some(Ok(update)) => {
retry_delay = Duration::from_secs(1);
if matches!(update.update_oneof, Some(UpdateOneof::Ping(_))) {
if let Err(error) = subscribe_tx
.send(SubscribeRequest {
ping: Some(SubscribeRequestPing { id: 1 }),
..Default::default()
})
.await
{
eprintln!("Ping response error: {error}");
return;
}
}
if matches!(update.update_oneof, Some(UpdateOneof::Ping(_)) | Some(UpdateOneof::Pong(_))) {
continue;
}
if let Some(UpdateOneof::Slot(ref slot_update)) = update.update_oneof {
tracked_slot = slot_update.slot;
continue;
}
if let Err(error) = updates_tx.send(update.clone()).await {
eprintln!("Update consumer stopped: {error}");
return;
}
}
Some(Err(error)) => {
eprintln!("Stream error: {error}");
return;
}
None => {
println!("Stream ended cleanly");
break;
}
}
}
}
}
}
Err(error) => {
eprintln!("Subscribe error: {error}");
return;
}
};
println!("Disconnected; retrying after bounded backoff...");
tokio::time::sleep(retry_delay).await;
retry_delay = (retry_delay * 2).min(max_retry_delay);
}
}
async fn dispatcher_loop(mut updates_rx: mpsc::Receiver<SubscribeUpdate>) {
loop {
if let Some(update) = updates_rx.recv().await {
match update.update_oneof {
Some(UpdateOneof::Transaction(tx)) => {
let sig = tx
.transaction
.and_then(|t| Signature::try_from(t.signature.as_slice()).ok())
.unwrap_or_default();
println!("Slot: {} | Signature: {}", tx.slot, sig);
}
_ => {
println!("Received update: {:?}", update);
}
}
}
}
}उदाहरणों में योगदान
कोई उपयोगी pattern मिला? इसे इन docs में जोड़ने पर विचार करें।
Solana Stream SDK की repository: https://github.com/ValidatorsDAO/solana-stream
Solana Stream SDK की repository: https://github.com/ValidatorsDAO/solana-stream






