Updates
The Update enum and every type a datasource emits in Carbon 2.0.0, with all fields.
On this page
Datasources send (Update, DatasourceId) pairs into the pipeline. This page lists every type in carbon_ that makes up an update, field by field. You need it when you write a custom datasource or a processor for account deletions or block details. For how updates move through the pipeline, see Updates.
Source: crates/.
Update#
The payload a datasource sends into the pipeline.
#[derive(Debug, Clone)]
pub enum Update {
Account(AccountUpdate),
Transaction(Box<TransactionUpdate>),
AccountDeletion(AccountDeletion),
BlockDetails(BlockDetails),
}Details.
| Variant | Routed to | Emitted in 2.0.0 by |
|---|---|---|
Account | account pipes (account) | RPC GPA, Helius GPA V2, validator snapshot, and every streaming source for funded accounts |
Transaction | instruction pipes, then transaction pipes | RPC transaction crawler, RPC block crawler, RPC block subscribe, Yellowstone gRPC, Helius LaserStream, Helius Atlas WebSocket, Helius GTFA, stream message |
AccountDeletion | deletion pipes (account_) | Yellowstone gRPC, Helius LaserStream, Helius Atlas WebSocket, RPC program subscribe, stream message |
BlockDetails | block-details pipes (block_) | RPC block subscribe only |
Transaction is boxed because TransactionUpdate is large. update_ returns the matching UpdateType.
Example.
match &update {
Update::Account(account) => println!("account {}", account.pubkey),
Update::Transaction(tx) => println!("tx {}", tx.signature),
Update::AccountDeletion(deletion) => println!("closed {}", deletion.pubkey),
Update::BlockDetails(block) => println!("block {}", block.slot),
}See also. docs.rs, Datasources.
Update:: update_ type#
Returns the variant as an UpdateType.
pub fn update_type(&self) -> UpdateTypeDetails. Account → UpdateType::, Transaction → UpdateType::, AccountDeletion → UpdateType::, BlockDetails → UpdateType::.
Example.
assert_eq!(update.update_type(), UpdateType::AccountUpdate);See also. UpdateType.
UpdateType#
The kinds of update a datasource declares it can emit.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UpdateType {
AccountUpdate,
Transaction,
AccountDeletion,
BlockDetails,
}Details. A datasource returns these from Datasource::. In 2.0.0 the value is informational: the pipeline never calls update_ and never checks it against the pipes you registered or the updates the datasource sends. Several built-in datasources under-report (for example, RPC block subscribe declares only Transaction but also emits BlockDetails, and Yellowstone gRPC doesn't list AccountDeletion). Don't rely on it to decide what a datasource emits; use the table under Update.
Example.
fn update_types(&self) -> Vec<UpdateType> {
vec![UpdateType::AccountUpdate, UpdateType::AccountDeletion]
}See also. Datasource, docs.rs.
AccountUpdate#
The state of one account at a slot.
#[derive(Debug, Clone)]
pub struct AccountUpdate {
pub pubkey: Pubkey,
pub account: Account,
pub slot: u64,
pub transaction_signature: Option<Signature>,
}Details.
| Field | Type | Meaning |
|---|---|---|
pubkey | solana_ | Address of the account. |
account | solana_ | Full account: lamports, data, owner, executable, rent_epoch. |
slot | u64 | Slot the state was observed at. For GPA and snapshot sources, the slot of the RPC context or the snapshot. |
transaction_ | Option< | Transaction that wrote this state. Yellowstone and LaserStream fill it from the update's txn_ when present; every other built-in datasource sends None. DeduplicationFilter only deduplicates accounts that have it. |
The pipeline turns AccountUpdate into an AccountMetadata plus the raw Account before calling your account decoder.
Example.
let update = AccountUpdate {
pubkey,
account: Account { lamports: 1_000_000, data, owner, executable: false, rent_epoch: 0 },
slot,
transaction_signature: None,
}
.into_update();See also. into_update, docs.rs.
AccountUpdate:: into_ update#
Wraps the account update in an Update, turning zero-lamport accounts into deletions.
pub fn into_update(self) -> UpdateDetails. Returns Update:: with the same four fields when account., and Update:: otherwise. Custom datasources should always build updates this way so closures reach deletion processors, as the built-in streaming datasources do. The GPA and snapshot datasources build Update:: directly, which is safe because they only return live accounts.
Example.
let update = AccountUpdate { pubkey, account, slot, transaction_signature: None }.into_update();
sender
.send((update, id.clone()))
.await
.map_err(|e| Error::FailedToConsumeDatasource(e.to_string()))?;See also. AccountDeletion, Custom datasource.
TransactionUpdate#
A confirmed transaction with its status metadata.
#[derive(Debug, Clone)]
pub struct TransactionUpdate {
pub signature: Signature,
pub transaction: VersionedTransaction,
pub meta: TransactionStatusMeta,
pub is_vote: bool,
pub slot: u64,
pub index: Option<u64>,
pub block_time: Option<i64>,
pub block_hash: Option<Hash>,
}Details.
| Field | Type | Meaning |
|---|---|---|
signature | solana_ | First signature of the transaction. |
transaction | solana_ | Signatures and message (legacy, V0 or V1). The pipeline reads the fee payer and instructions from transaction.. |
meta | solana_ | Execution result: status, fee, balances, inner_, log_, token balances, rewards, loaded_, return_data, compute_, cost_units. Inner instructions and loaded addresses must be present for CPIs and V0 lookup-table accounts to resolve. |
is_vote | bool | Vote transaction flag. Only Yellowstone and LaserStream set it from the source; the RPC and Helius GTFA datasources always set false. |
slot | u64 | Slot the transaction landed in. |
index | Option<u64> | Position of the transaction in its block. Some for RPC block crawler, RPC block subscribe, Yellowstone and LaserStream; GTFA passes the provider's value; None for the RPC transaction crawler and Helius Atlas. SlotRangeFilter uses it for transaction-index bounds. |
block_time | Option<i64> | Unix timestamp in seconds, when the source provides it. |
block_hash | Option< | Hash of the block. Only the RPC block crawler and RPC block subscribe set it; every other built-in datasource sends None. |
The pipeline converts the update into a TransactionMetadata. If the message has no account keys, the conversion fails with MissingFeePayer.
Example.
let update = Update::Transaction(Box::new(TransactionUpdate {
signature,
transaction,
meta,
is_vote: false,
slot,
index: Some(tx_index),
block_time: Some(block_time),
block_hash: None,
}));See also. Transactions, docs.rs.
AccountDeletion#
An account that was closed, with its final state.
#[derive(Debug, Clone)]
pub struct AccountDeletion {
pub pubkey: Pubkey,
pub account: Account,
pub slot: u64,
pub transaction_signature: Option<Signature>,
}Details.
| Field | Type | Meaning |
|---|---|---|
pubkey | solana_ | Address of the closed account. |
account | solana_ | Final state as the source reported it, unchanged: lamports is 0. Programs often clear the data and reassign the account to the System Program when closing it, so owner and data may no longer identify the program. New in 2.0.0. |
slot | u64 | Slot of the closure. |
transaction_ | Option< | Closing transaction, when the source reports it. |
Deletion processors receive &AccountDeletion directly, with no decoder. To know which program the account belonged to, check account. or keep your own index of addresses.
Example.
impl Processor<AccountDeletion> for DeletionLogger {
async fn process(&mut self, deletion: &AccountDeletion) -> CarbonResult<()> {
println!("closed {} at slot {}", deletion.pubkey, deletion.slot);
Ok(())
}
}See also. account_, docs.rs.
BlockDetails#
Block-level metadata.
#[derive(Debug, Clone)]
pub struct BlockDetails {
pub slot: u64,
pub block_hash: Option<Hash>,
pub previous_block_hash: Option<Hash>,
pub rewards: Option<Rewards>,
pub num_reward_partitions: Option<u64>,
pub block_time: Option<i64>,
pub block_height: Option<u64>,
}Details.
| Field | Type | Meaning |
|---|---|---|
slot | u64 | Slot of the block. |
block_hash | Option< | Hash of this block. |
previous_ | Option< | Hash of the parent block. |
rewards | Option< | Block rewards (Vec<Reward>), when the subscription requests them. |
num_ | Option<u64> | Number of partitions for partitioned epoch rewards, when reported. |
block_time | Option<i64> | Unix timestamp in seconds. |
block_ | Option<u64> | Block height. |
In 2.0.0 only RpcBlockSubscribe emits block details, one per block, before that block's transactions. It copies every field from the RPC UiConfirmedBlock.
Example.
impl Processor<BlockDetails> for BlockLogger {
async fn process(&mut self, block: &BlockDetails) -> CarbonResult<()> {
println!("block {} height={:?}", block.slot, block.block_height);
Ok(())
}
}See also. block_, docs.rs.
DatasourceId#
Identifies the datasource an update came from.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DatasourceId(String);
impl DatasourceId {
pub fn new_unique() -> Self;
pub fn new_named(name: &str) -> Self;
}Details.
| Constructor | Value |
|---|---|
new_ | A random UUID v4 string. PipelineBuilder:: uses this. |
new_ | The string you pass. Two IDs with the same name are equal. |
The inner String is private and there is no accessor or Display impl; compare IDs with == or print them with {:?} (which shows DatasourceId("name")). Filters receive the ID in FilterContext, and Datasource:: receives it so the datasource can tag what it sends.
Example.
let geyser = DatasourceId::new_named("geyser");
assert_eq!(geyser, DatasourceId::new_named("geyser"));
println!("{geyser:?}");See also. datasource_, DatasourceFilter, docs.rs.
DatasourceDisconnection#
Describes a gap in a streaming datasource after it reconnects.
#[derive(Debug, Clone)]
pub struct DatasourceDisconnection {
pub source: String,
pub disconnect_time: DateTime<Utc>,
pub last_slot_before_disconnect: Slot,
pub first_slot_after_reconnect: Slot,
pub missed_slots: u64,
}Details. This is not an Update variant and never goes through the pipeline. Two datasources in 2.0.0 can report gaps: RpcBlockSubscribe (through RpcBlockSubscribe::) and YellowstoneGrpcGeyserClient (through the disconnect_ argument of new). Each takes a tokio:: and sends one event with the first message after a reconnect. You read the receiver yourself, for example to backfill the missed range with a crawler. Yellowstone sends with try_send, so the event is dropped if your channel is full.
| Field | Type | Meaning |
|---|---|---|
source | String | "rpc-websocket" for RPC block subscribe, "yellowstone-grpc" for Yellowstone. |
disconnect_ | chrono:: | When the stream was detected as closed or timed out. |
last_ | u64 (solana_) | Last slot processed before the disconnect. |
first_ | u64 | Slot of the first message received after reconnecting. |
missed_ | u64 | first_ (saturating). The slots strictly between the two are the ones you may have missed, so the true gap is one less than this value. |
Example.
let (tx, mut rx) = tokio::sync::mpsc::channel::<DatasourceDisconnection>(16);
tokio::spawn(async move {
while let Some(gap) = rx.recv().await {
eprintln!(
"{} missed {} slots ({}..{})",
gap.source, gap.missed_slots, gap.last_slot_before_disconnect, gap.first_slot_after_reconnect
);
}
});
let datasource = RpcBlockSubscribe::with_disconnect_notifier(ws_url, filters, tx);See also. Production, docs.rs.