RPC blockSubscribe
Receive every confirmed block, or only transactions that mention a program, over a Solana RPC WebSocket with blockSubscribe.
On this page
RpcBlockSubscribe opens a blockSubscribe WebSocket subscription and emits a block-details update for every block, followed by a transaction update for each successful transaction in it. Use it for live transactions from an RPC node that has block subscriptions enabled, when you don't have a Geyser gRPC endpoint.
| Crate | carbon-rpc-block-subscribe-datasource 2.0.0 |
| Install | cargo add carbon-rpc-block-subscribe-datasource@2. |
| Emits | BlockDetails, Transaction. update_ declares only Transaction. |
| Mode | Live. Runs until cancelled. |
| Provider | An RPC WebSocket endpoint that supports blockSubscribe. |
| API docs | docs.rs |
| Source | datasources/ |
Quick example#
This program subscribes to confirmed blocks that mention the Orca Whirlpool program, prints each block and each swapV2 instruction. It compiles as shown; to run it, set RPC_WS_URL to an endpoint that supports blockSubscribe.
cargo new block-subscribe && cd block-subscribe
cargo add carbon-core@2.0.0 carbon-log-metrics@2.0.0 carbon-orca-whirlpool-decoder@2.0.0 carbon-rpc-block-subscribe-datasource@2.0.0
cargo add solana-client@=4.2.2
cargo add tokio@1 --features full
cargo add env_logger@0.11 log@0.4use {
carbon_core::{
datasource::BlockDetails, error::CarbonResult,
instruction::InstructionProcessorInputType, pipeline::Pipeline, processor::Processor,
},
carbon_log_metrics::LogMetrics,
carbon_orca_whirlpool_decoder::{
instructions::OrcaWhirlpoolInstruction, OrcaWhirlpoolDecoder, PROGRAM_ID,
},
carbon_rpc_block_subscribe_datasource::{Filters, RpcBlockSubscribe},
solana_client::rpc_config::{
CommitmentConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter, TransactionDetails,
UiTransactionEncoding,
},
std::{env, sync::Arc},
};
#[tokio::main]
async fn main() -> CarbonResult<()> {
env_logger::init();
let config = RpcBlockSubscribeConfig {
commitment: Some(CommitmentConfig::confirmed()),
encoding: Some(UiTransactionEncoding::Base64), // required: Carbon can't decode JSON payloads
transaction_details: Some(TransactionDetails::Full),
show_rewards: Some(false),
max_supported_transaction_version: Some(1),
};
let datasource = RpcBlockSubscribe::new(
env::var("RPC_WS_URL").expect("RPC_WS_URL must be set"),
Filters::new(
RpcBlockSubscribeFilter::MentionsAccountOrProgram(PROGRAM_ID.to_string()),
Some(config),
),
);
Pipeline::builder()
.datasource(datasource)
.metrics(Arc::new(LogMetrics::new()))
.instruction(OrcaWhirlpoolDecoder, SwapLogger)
.block_details(BlockLogger)
.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(())
}
}
struct BlockLogger;
impl Processor<BlockDetails> for BlockLogger {
async fn process(&mut self, block: &BlockDetails) -> CarbonResult<()> {
println!("block {} time={:?} hash={:?}", block.slot, block.block_time, block.block_hash);
Ok(())
}
}RPC_WS_URL=wss://your-node.example.com RUST_LOG=info cargo runAgainst the public endpoint the subscription is refused and retried every few seconds:
[ERROR carbon_rpc_block_subscribe_datasource] Failed to subscribe to block updates: SubscribeFailed { reason: "Method not found (-32601)", message: "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":1}" }Configuration#
Constructors#
pub fn new(rpc_ws_url: String, filters: Filters) -> Self
pub fn with_disconnect_notifier(
rpc_ws_url: String,
filters: Filters,
disconnect_notifier: tokio::sync::mpsc::Sender<DatasourceDisconnection>,
) -> Self
pub fn with_stream_timeout(self, timeout: Duration) -> Self| Field | Type | Default | Meaning |
|---|---|---|---|
rpc_ws_url | String | — | WebSocket endpoint. |
filters | Filters | — | Block filter and subscription config. |
disconnect_ | Option< | None | Receives one event per reconnect with the slot gap. See below. |
stream_ | Duration | 30 s (DEFAULT_) | With no message for this long, the connection is treated as dead and reopened. |
Filters#
pub const fn new(
block_filter: RpcBlockSubscribeFilter,
block_subscribe_config: Option<RpcBlockSubscribeConfig>,
) -> SelfRpcBlockSubscribeFilter (from solana_) is All or MentionsAccountOrProgram(String), the latter taking a base58 address. RpcBlockSubscribeConfig:
| Field | Type | Recommended | Meaning |
|---|---|---|---|
commitment | Option< | confirmed | None uses the server default (finalized). |
encoding | Option< | Some(Base64) | Required in practice. JSON encodings can't be decoded: every transaction logs Failed to decode transaction. |
transaction_ | Option< | Some(Full) | Anything else yields no transactions to decode. |
show_ | Option< | as needed | Rewards are copied into BlockDetails.. |
max_ | Option<u8> | Some(1) | Needed for blocks with versioned transactions. |
DatasourceDisconnection#
Sent with try_send on the first block after a reconnect: source ("rpc-websocket"), disconnect_, last_, first_, missed_. Use it to trigger a backfill of the gap with RpcBlockCrawler.
Behaviour#
- Updates per block. One
BlockDetails(slot, block hash, previous block hash, rewards, reward partitions, block time, block height), then oneTransactionper transaction whose status isOk. Transactions carry the block'sblock_timeandblock_hash,is_, andvote: false indexset to the position in the notification's transaction list. WithMentionsAccountOrProgramthat list contains only matching transactions, soindexis not the position in the full block. update_. Declares onlytypes() Transaction, althoughBlockDetailsis emitted too. The pipeline does not check this list, so.processors receive the blocks.block_ details(…) - Reconnects. On stream close or
stream_, the datasource waits 3 seconds and reconnects. Connection failures are retried every 3 seconds up to 10 times, subscription failures up to 11 times, thentimeout consumereturns an error. The counter resets after a successful subscription. Missed blocks are not replayed. - Backpressure. Updates use
try_send. If theBlockDetailssend fails, the subscription is dropped and reopened. If a transaction send fails, the rest of that block's transactions are dropped. Both logError sending …. - Finishing. Never finishes on its own.
- Metrics.
block_,subscribe_ blocks_ received_ total block_(counters),subscribe_ transactions_ processed_ total block_,subscribe_ block_ process_ time_ nanoseconds block_(histograms).subscribe_ transaction_ process_ time_ nanoseconds
Troubleshooting#
Failed to subscribe to block updates: … Method not found (-32601)#
The endpoint doesn't support blockSubscribe. The public mainnet endpoint never does. Use a node with --rpc-pubsub-enable-block-subscription, a provider that exposes it, or switch to Yellowstone gRPC or the block crawler.
Every transaction logs Failed to decode transaction#
Set encoding: Some(UiTransactionEncoding:: in RpcBlockSubscribeConfig.
Block stream timeout - no messages for 30s#
The node stopped sending blocks, often because the filter matched nothing for 30 seconds. With a narrow MentionsAccountOrProgram filter on a quiet program, raise the timeout with ..
Blocks are missing after a reconnect#
Expected: nothing is replayed. Pass a disconnect_ and backfill last_ with the block crawler.
See also#
- Stream with RPC
- RPC block crawler
- Processor inputs for
BlockDetails - Datasources concept and update types