Yellowstone gRPC
Stream accounts, transactions and blocks from any Yellowstone (Dragon's Mouth) Geyser gRPC endpoint with the lowest latency Carbon offers.
On this page
- Quick example
- Configuration
- YellowstoneGrpcGeyserClient::
new - BlockFilters
- YellowstoneGrpcClientConfig
- Reconnects
- Detect gaps with DatasourceDisconnection
- Behaviour
- Troubleshooting
- error consuming datasource: FailedToConsumeDatasource("gRPC transport error: transport error")
- The log fills with Geyser stream error: Status { … }
- Geyser stream error … invalid compression flag … status: 501 Not Implemented
- Panic: Could not automatically determine the process-level CryptoProvider
- Updates are missing under load
- Transactions that later disappear
- See also
YellowstoneGrpcGeyserClient opens one Yellowstone Geyser gRPC subscription and turns its account, transaction and block messages into Carbon updates. Use it for production real-time indexing when you have a Yellowstone endpoint: your own validator with the plugin, or a provider such as Triton, Helius, QuickNode or Shyft. For Helius LaserStream with slot replay, use Helius LaserStream instead.
| Crate | carbon-yellowstone-grpc-datasource 2.0.0 |
| Install | cargo add carbon-yellowstone-grpc-datasource@2. |
| Emits | AccountUpdate, AccountDeletion, Transaction |
| Mode | Live. Runs until cancelled. |
| Provider | A Yellowstone gRPC endpoint and, usually, an x-token. Paid with most providers. |
| API docs | docs.rs |
| Source | datasources/ |
Quick example#
This program subscribes to confirmed, successful, non-vote transactions that include the Orca Whirlpool program and prints each swapV2. It compiles as shown; to run it, set GEYSER_URL (for example https:/) and X_TOKEN.
cargo new yellowstone && cd yellowstone
cargo add carbon-core@2.0.0 carbon-log-metrics@2.0.0 carbon-orca-whirlpool-decoder@2.0.0 carbon-yellowstone-grpc-datasource@2.0.0
cargo add yellowstone-grpc-proto@=12.7.0
cargo add tokio@1 --features full
cargo add env_logger@0.11 log@0.4use {
carbon_core::{
error::CarbonResult, instruction::InstructionProcessorInputType, pipeline::Pipeline,
processor::Processor,
},
carbon_log_metrics::LogMetrics,
carbon_orca_whirlpool_decoder::{
instructions::OrcaWhirlpoolInstruction, OrcaWhirlpoolDecoder, PROGRAM_ID,
},
carbon_yellowstone_grpc_datasource::{
BlockFilters, YellowstoneGrpcClientConfig, YellowstoneGrpcGeyserClient,
},
std::{collections::HashMap, env, sync::Arc},
yellowstone_grpc_proto::geyser::{CommitmentLevel, SubscribeRequestFilterTransactions},
};
#[tokio::main]
async fn main() -> CarbonResult<()> {
env_logger::init();
let mut transaction_filters = HashMap::new();
transaction_filters.insert(
"orca_whirlpool".to_string(),
SubscribeRequestFilterTransactions {
vote: Some(false),
failed: Some(false),
account_include: vec![PROGRAM_ID.to_string()],
..Default::default()
},
);
let datasource = YellowstoneGrpcGeyserClient::new(
env::var("GEYSER_URL").expect("GEYSER_URL must be set"),
env::var("X_TOKEN").ok(),
Some(CommitmentLevel::Confirmed),
HashMap::new(), // account filters
transaction_filters,
BlockFilters::default(),
YellowstoneGrpcClientConfig::default(),
None, // disconnect notifier
None, // stream timeout: 30 s
);
Pipeline::builder()
.datasource(datasource)
.metrics(Arc::new(LogMetrics::new()))
.instruction(OrcaWhirlpoolDecoder, SwapLogger)
.build()?
.run()
.await
}
struct SwapLogger;
impl Processor<InstructionProcessorInputType<'_, OrcaWhirlpoolInstruction>> for SwapLogger {
async fn process(
&mut self,
input: &InstructionProcessorInputType<'_, OrcaWhirlpoolInstruction>,
) -> CarbonResult<()> {
let tx = &input.metadata.transaction_metadata;
if let OrcaWhirlpoolInstruction::SwapV2 { data, accounts, .. } = input.decoded_instruction {
println!("slot {} swapV2 pool={} amount={}", tx.slot, accounts.whirlpool, data.amount);
}
Ok(())
}
}GEYSER_URL=https://your-yellowstone-endpoint:443 X_TOKEN=your-x-token RUST_LOG=info cargo runEach matching swap prints one slot … swapV2 pool=… amount=… line as its slot is confirmed. For a step-by-step walkthrough, see Stream with Yellowstone.
Configuration#
YellowstoneGrpcGeyserClient:: new#
pub fn new(
endpoint: String,
x_token: Option<String>,
commitment: Option<CommitmentLevel>,
account_filters: HashMap<String, SubscribeRequestFilterAccounts>,
transaction_filters: HashMap<String, SubscribeRequestFilterTransactions>,
block_filters: BlockFilters,
geyser_config: YellowstoneGrpcClientConfig,
disconnect_notifier: Option<mpsc::Sender<DatasourceDisconnection>>,
stream_timeout: Option<Duration>,
) -> Self| Parameter | Type | Default | Meaning |
|---|---|---|---|
endpoint | String | — | gRPC URL, including scheme and port. |
x_token | Option< | — | Sent as the x-token header. None for endpoints without auth. |
commitment | Option< | — | Processed, Confirmed or Finalized. None leaves it to the server (Yellowstone defaults to Processed, which can include transactions from forks that are later dropped). |
account_ | HashMap< | — | Named account filters. The key is a label of your choice. Empty: no account stream. |
transaction_ | HashMap< | — | Named transaction filters. Empty: no transaction stream. |
block_ | BlockFilters | BlockFilters:: | Block subscriptions. See below. |
geyser_ | YellowstoneGrpcClientConfig | default() | Connection settings. See below. |
disconnect_ | Option< | None | Receives one event per reconnect with the slot gap. |
stream_ | Option< | 30 s | With no message for this long, the stream is treated as dead and reopened. |
Filter types come from yellowstone_ (yellowstone-grpc-proto 12.7.0). They are prost messages, so . fills the fields you don't set.
SubscribeRequestFilterTransactions:
| Field | Type | Meaning |
|---|---|---|
vote | Option< | Some(false) excludes vote transactions, Some(true) keeps only votes. |
failed | Option< | Some(false) excludes failed transactions. Carbon does not drop failed transactions from this stream on its own. |
signature | Option< | A single transaction signature. |
account_ | Vec<String> | Match transactions that use any of these addresses. |
account_ | Vec<String> | Drop transactions that use any of these addresses. |
account_ | Vec<String> | Match only transactions that use all of these addresses. |
cuckoo_, token_ | Provider extensions; see your provider's docs. |
SubscribeRequestFilterAccounts:
| Field | Type | Meaning |
|---|---|---|
account | Vec<String> | Specific account addresses. |
owner | Vec<String> | Accounts owned by these programs. |
filters | Vec< | Filter::, Filter::, Filter::, Filter::. |
nonempty_ | Option< | Only updates caused by a transaction. |
For example, to stream every Orca Whirlpool pool account:
use yellowstone_grpc_proto::geyser::{
subscribe_request_filter_accounts_filter::Filter, SubscribeRequestFilterAccounts,
SubscribeRequestFilterAccountsFilter,
};
let mut account_filters = HashMap::new();
account_filters.insert(
"whirlpools".to_string(),
SubscribeRequestFilterAccounts {
owner: vec![PROGRAM_ID.to_string()],
filters: vec![SubscribeRequestFilterAccountsFilter {
filter: Some(Filter::Datasize(653)),
}],
..Default::default()
},
);BlockFilters#
| Field | Type | Default | Meaning |
|---|---|---|---|
filters | HashMap< | empty | Named block filters: account_, include_, include_, include_. |
failed_ | Option< | None (keep) | Some(false) drops failed transactions from block messages. |
YellowstoneGrpcClientConfig#
pub const fn new(
compression: Option<CompressionEncoding>,
connect_timeout: Option<Duration>,
timeout: Option<Duration>,
max_decoding_message_size: Option<usize>,
tls_config: Option<ClientTlsConfig>,
tcp_nodelay: Option<bool>,
) -> Self
pub fn with_reconnect(self, reconnect: ReconnectConfig) -> Self| Field | Type | Default | Meaning |
|---|---|---|---|
compression | Option< | None | Send and accept compressed messages (tonic::, re-exported by yellowstone_). |
connect_ | Option< | 15 s | Connection timeout. |
timeout | Option< | 15 s | Request timeout. |
max_ | Option<, bytes | None: tonic's limit of 4 MiB (4,194,304 bytes) | Largest message the client accepts. Raise it when large blocks or accounts fail; see below. |
tls_config | Option< | None (system and webpki roots) | Custom TLS configuration. |
tcp_nodelay | Option< | None | Set TCP_NODELAY. |
reconnect | Option< | None | Reconnect inside the client library and replay missed data. Set with with_. |
A message over max_ ends the stream with tonic's OutOfRange status, which the datasource logs as a stream error before resubscribing. The status message has this form, with your sizes:
Error, decoded message length too large: found 5242880 bytes, the limit is: 4194304 bytesBlock subscriptions and large program accounts are the usual causes. Raise the limit when you build the config:
// Accept messages up to 64 MiB instead of tonic's 4 MiB default.
let config = YellowstoneGrpcClientConfig::new(None, None, None, Some(64 * 1024 * 1024), None, None);Reconnects#
Without any configuration, the datasource resubscribes on its own after a stream error and never gives up, but data sent during the gap is lost (see Behaviour). To have the client library also replay what you missed, opt in with with_.
ReconnectConfig comes from yellowstone-grpc-client, which you add with cargo add yellowstone-grpc-client@=13.. Its default retries 3 times with a 10 ms backoff and recovers missed messages for up to 250 slots:
use yellowstone_grpc_client::ReconnectConfig;
let geyser_config = YellowstoneGrpcClientConfig::default().with_reconnect(ReconnectConfig::default());Detect gaps with DatasourceDisconnection#
With a disconnect_, the datasource sends { source: "yellowstone-grpc", disconnect_ on the first message after a reconnect. Read it in your own task:
let (disconnect_tx, mut disconnect_rx) = tokio::sync::mpsc::channel::<DatasourceDisconnection>(16);
tokio::spawn(async move {
while let Some(gap) = disconnect_rx.recv().await {
log::warn!(
"missed {} slots ({}..{})",
gap.missed_slots, gap.last_slot_before_disconnect, gap.first_slot_after_reconnect
);
}
});
// pass Some(disconnect_tx) as `disconnect_notifier`Behaviour#
- Connecting.
consumeconnects before spawning the stream task. If the first connection fails, it returnsFailedToConsumeDatasource("gRPC transport error: …"); a pipeline with only this datasource then exits. - Subscription. One
SubscribeRequestwith your account, transaction and block filters and commitment. When the server sends a ping, the datasource answers by resending the request with a ping. - Account messages become
AccountUpdate(orAccountDeletionat zero lamports) with the message's slot and the causingtransaction_.signature - Transaction messages become
Transactionupdates withis_voteandindexfrom Geyser, andblock_time/block_hashset toNone. Failed and vote transactions pass through unless your filter excludes them. - Block messages produce a
Transactionupdate for each transaction (with the block'sblock_time) and an account update for each account in the block. NoBlockDetailsupdates are emitted. - Ordering. As delivered by the server: slot order for a given commitment, but accounts and transactions from different filters interleave.
- Reconnects. When the stream errors, closes or is silent for
stream_, the datasource subscribes again on the same client right away. If subscribing fails, it backs off from 100 ms, doubling up to 3 s, and never gives up. Withouttimeout reconnect, data during the gap is lost;disconnect_tells you which slots.notifier - Backpressure. Every update uses
try_send. When the pipeline channel is full the update is dropped withFailed to send transaction update …orFailed to send account event …. Size the pipeline'schannel_for your peak rate.buffer_ size - Finishing. Never finishes on its own.
- Metrics. Counters
yellowstone_,grpc_ account_ updates_ received_ total yellowstone_,grpc_ account_ deletions_ received_ total yellowstone_; histogramsgrpc_ transaction_ updates_ received_ total yellowstone_,grpc_ account_ process_ time_ nanoseconds yellowstone_,grpc_ account_ deletion_ process_ time_ nanoseconds yellowstone_.grpc_ transaction_ process_ time_ nanoseconds
Troubleshooting#
error consuming datasource: FailedToConsumeDatasource("gRPC transport error: transport error")#
The first connection failed and the pipeline exited. Check the URL (scheme and port, for example https:/), that the host is reachable, and the provider's IP allowlist.
The log fills with Geyser stream error: Status { … }#
The server rejects the subscription; the Status code and message say why. A missing or wrong token is the most common cause: pass it as x_token. A filter the provider doesn't allow (too many accounts, an unsupported field) is the next. The datasource resubscribes immediately after each stream error, so a persistent problem produces many log lines per second.
Geyser stream error … invalid compression flag … status: 501 Not Implemented#
The URL points at something that isn't a Yellowstone gRPC server, such as a JSON-RPC endpoint. Use the provider's gRPC URL.
Panic: Could not automatically determine the process-level CryptoProvider#
Two rustls crypto backends ended up in your dependency tree. Add rustls (cargo add rustls@0.) and install one provider at the top of main, before building the pipeline:
rustls::crypto::ring::default_provider()
.install_default()
.expect("failed to install rustls crypto provider");Updates are missing under load#
Look for Failed to send transaction update in the logs. Raise Pipeline::, make processors faster, or narrow your filters.
Transactions that later disappear#
With CommitmentLevel:: (or None) you also receive transactions from forks that never confirm. Use Confirmed unless you handle rollbacks.
See also#
- Stream with Yellowstone
- Helius LaserStream: same protocol, with slot replay.
- Production and Prometheus
- Datasources concept and update types