feat: add Compute Budget Program instruction parsing support

Add support for parsing SetComputeUnitLimit and SetComputeUnitPrice instructions from Solana's Compute Budget Program. Update event parser to handle compute budget instructions and bump version to 1.1.3.
This commit is contained in:
ysq
2025-11-16 22:49:37 +08:00
parent 1ec7f845a5
commit 4b019e5e01
8 changed files with 174 additions and 39 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solana-streamer-sdk"
version = "1.1.2"
version = "1.1.3"
edition = "2021"
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
repository = "https://github.com/0xfnzero/solana-streamer"
+2 -2
View File
@@ -109,14 +109,14 @@ Add the dependency to your `Cargo.toml`:
```toml
# Add to your Cargo.toml
solana-streamer-sdk = { path = "./solana-streamer", version = "1.1.2" }
solana-streamer-sdk = { path = "./solana-streamer", version = "1.1.3" }
```
### Use crates.io
```toml
# Add to your Cargo.toml
solana-streamer-sdk = "1.1.2"
solana-streamer-sdk = "1.1.3"
```
## 🔄 Migration Guide
+2 -2
View File
@@ -108,14 +108,14 @@ git clone https://github.com/0xfnzero/solana-streamer
```toml
# 添加到您的 Cargo.toml
solana-streamer-sdk = { path = "./solana-streamer", version = "1.1.2" }
solana-streamer-sdk = { path = "./solana-streamer", version = "1.1.3" }
```
### 使用 crates.io
```toml
# 添加到您的 Cargo.toml
solana-streamer-sdk = "1.1.2"
solana-streamer-sdk = "1.1.3"
```
## 🔄 迁移指南
@@ -139,6 +139,8 @@ pub enum EventType {
// Common events
BlockMeta,
SetComputeUnitLimit,
SetComputeUnitPrice,
Unknown,
}
@@ -233,6 +235,8 @@ impl fmt::Display for EventType {
EventType::TokenAccount => write!(f, "TokenAccount"),
EventType::NonceAccount => write!(f, "NonceAccount"),
EventType::BlockMeta => write!(f, "BlockMeta"),
EventType::SetComputeUnitLimit => write!(f, "SetComputeUnitLimit"),
EventType::SetComputeUnitPrice => write!(f, "SetComputeUnitPrice"),
EventType::Unknown => write!(f, "Unknown"),
}
}
@@ -1,6 +1,33 @@
use crate::streaming::event_parser::common::high_performance_clock::elapsed_micros_since;
use crate::streaming::event_parser::common::types::{EventType, ProtocolType};
use crate::streaming::event_parser::common::EventMetadata;
use crate::streaming::event_parser::core::traits::DexEvent;
use crate::streaming::event_parser::protocols::block::block_meta_event::BlockMetaEvent;
use borsh::BorshDeserialize;
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
// Compute Budget Program ID
pub const COMPUTE_BUDGET_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("ComputeBudget111111111111111111111111111111");
/// SetComputeUnitLimit 事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct SetComputeUnitLimitEvent {
#[borsh(skip)]
pub metadata: EventMetadata,
/// 请求的计算单元数量
pub units: u32,
}
/// SetComputeUnitPrice 事件
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct SetComputeUnitPriceEvent {
#[borsh(skip)]
pub metadata: EventMetadata,
/// 每个计算单元的价格 (micro-lamports)
pub micro_lamports: u64,
}
pub struct CommonEventParser {}
@@ -15,4 +42,42 @@ impl CommonEventParser {
block_meta_event.metadata.handle_us = elapsed_micros_since(recv_us);
DexEvent::BlockMetaEvent(block_meta_event)
}
/// 解析 Compute Budget 指令
pub fn parse_compute_budget_instruction(
instruction_data: &[u8],
mut metadata: EventMetadata,
) -> Option<DexEvent> {
if instruction_data.is_empty() {
return None;
}
// 设置 protocol 为 Common
metadata.protocol = ProtocolType::Common;
// Compute Budget 指令使用单字节判别器
match instruction_data[0] {
// SetComputeUnitLimit: discriminator = 2
2 => {
if instruction_data.len() < 5 {
return None;
}
let units = u32::from_le_bytes(instruction_data[1..5].try_into().ok()?);
metadata.event_type = EventType::SetComputeUnitLimit;
let event = SetComputeUnitLimitEvent { metadata, units };
Some(DexEvent::SetComputeUnitLimitEvent(event))
}
// SetComputeUnitPrice: discriminator = 3
3 => {
if instruction_data.len() < 9 {
return None;
}
let micro_lamports = u64::from_le_bytes(instruction_data[1..9].try_into().ok()?);
metadata.event_type = EventType::SetComputeUnitPrice;
let event = SetComputeUnitPriceEvent { metadata, micro_lamports };
Some(DexEvent::SetComputeUnitPriceEvent(event))
}
_ => None,
}
}
}
@@ -9,6 +9,7 @@
use crate::streaming::event_parser::{
common::EventMetadata,
core::common_event_parser::{CommonEventParser, COMPUTE_BUDGET_PROGRAM_ID},
protocols::{
bonk::parser as bonk, meteora_damm_v2::parser as meteora_damm_v2, pumpfun::parser as pumpfun,
pumpswap::parser as pumpswap, raydium_amm_v4::parser as raydium_amm_v4,
@@ -191,6 +192,28 @@ impl EventDispatcher {
}
}
/// 检查是否为 Compute Budget Program
#[inline]
pub fn is_compute_budget_program(program_id: &Pubkey) -> bool {
program_id == &COMPUTE_BUDGET_PROGRAM_ID
}
/// 解析 Compute Budget 指令
///
/// # 参数
/// - `instruction_data`: 指令数据
/// - `metadata`: 事件元数据
///
/// # 返回
/// 解析成功返回 `Some(DexEvent)`,否则返回 `None`
#[inline]
pub fn dispatch_compute_budget_instruction(
instruction_data: &[u8],
metadata: EventMetadata,
) -> Option<DexEvent> {
CommonEventParser::parse_compute_budget_instruction(instruction_data, metadata)
}
/// 获取指定协议的 program_id
#[inline]
pub fn get_program_id(protocol: Protocol) -> Pubkey {
+68 -34
View File
@@ -325,28 +325,13 @@ impl EventParser {
return Ok(());
}
// 使用 EventDispatcher 匹配协议
let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) {
Some(p) => p,
None => return Ok(()),
};
let is_cu_program = EventDispatcher::is_compute_budget_program(&program_id);
// 检查指令数据长度(至少需要 8 字节的 discriminator
if instruction.data.len() < 8 {
if !is_cu_program && instruction.data.len() < 8 {
return Ok(());
}
// 提取 discriminator 和数据
let instruction_discriminator = &instruction.data[..8];
let instruction_data = &instruction.data[8..];
// 构建账户公钥列表
let account_pubkeys: Vec<Pubkey> = instruction
.accounts
.iter()
.filter_map(|&idx| accounts.get(idx as usize).copied())
.collect();
// 创建元数据
let timestamp = block_time.unwrap_or(Timestamp { seconds: 0, nanos: 0 });
let block_time_ms = timestamp.seconds * 1000 + (timestamp.nanos as i64) / 1_000_000;
@@ -364,6 +349,33 @@ impl EventParser {
transaction_index,
);
if is_cu_program {
if let Some(event) = EventDispatcher::dispatch_compute_budget_instruction(
&instruction.data,
metadata.clone(),
) {
callback(&event);
}
return Ok(());
}
// 使用 EventDispatcher 匹配协议
let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) {
Some(p) => p,
None => return Ok(()),
};
// 提取 discriminator 和数据
let instruction_discriminator = &instruction.data[..8];
let instruction_data = &instruction.data[8..];
// 构建账户公钥列表
let account_pubkeys: Vec<Pubkey> = instruction
.accounts
.iter()
.filter_map(|&idx| accounts.get(idx as usize).copied())
.collect();
// 使用 EventDispatcher 解析 instruction 事件
let mut event = match EventDispatcher::dispatch_instruction(
protocol.clone(),
@@ -483,28 +495,13 @@ impl EventParser {
return Ok(());
}
// 使用 EventDispatcher 匹配协议
let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) {
Some(p) => p,
None => return Ok(()),
};
let is_cu_program = EventDispatcher::is_compute_budget_program(&program_id);
// 检查指令数据长度(至少需要 8 字节的 discriminator
if instruction.data.len() < 8 {
if !is_cu_program && instruction.data.len() < 8 {
return Ok(());
}
// 提取 discriminator 和数据
let instruction_discriminator = &instruction.data[..8];
let instruction_data = &instruction.data[8..];
// 构建账户公钥列表
let account_pubkeys: Vec<Pubkey> = instruction
.accounts
.iter()
.filter_map(|&idx| accounts.get(idx as usize).copied())
.collect();
// 创建元数据
let timestamp = block_time.unwrap_or(Timestamp { seconds: 0, nanos: 0 });
let block_time_ms = timestamp.seconds * 1000 + (timestamp.nanos as i64) / 1_000_000;
@@ -522,6 +519,33 @@ impl EventParser {
transaction_index,
);
if is_cu_program {
if let Some(event) = EventDispatcher::dispatch_compute_budget_instruction(
&instruction.data,
metadata.clone(),
) {
callback(&event);
}
return Ok(());
}
// 使用 EventDispatcher 匹配协议
let protocol = match EventDispatcher::match_protocol_by_program_id(&program_id) {
Some(p) => p,
None => return Ok(()),
};
// 提取 discriminator 和数据
let instruction_discriminator = &instruction.data[..8];
let instruction_data = &instruction.data[8..];
// 构建账户公钥列表
let account_pubkeys: Vec<Pubkey> = instruction
.accounts
.iter()
.filter_map(|&idx| accounts.get(idx as usize).copied())
.collect();
// 使用 EventDispatcher 解析 instruction 事件
let mut event = match EventDispatcher::dispatch_instruction(
protocol.clone(),
@@ -621,6 +645,8 @@ impl EventParser {
// 使用 EventDispatcher 来匹配协议
if let Some(protocol) = EventDispatcher::match_protocol_by_program_id(program_id) {
protocols.contains(&protocol)
} else if EventDispatcher::is_compute_budget_program(program_id) {
return true;
} else {
false
}
@@ -648,6 +674,14 @@ impl EventParser {
}
DexEvent::PumpFunCreateTokenEvent(token_info)
}
DexEvent::PumpFunCreateV2TokenEvent(token_info) => {
add_dev_address(&signature, token_info.user);
if token_info.creator != Pubkey::default() && token_info.creator != token_info.user
{
add_dev_address(&signature, token_info.creator);
}
DexEvent::PumpFunCreateV2TokenEvent(token_info)
}
DexEvent::PumpFunTradeEvent(mut trade_info) => {
trade_info.is_dev_create_token_trade =
is_dev_address_in_signature(&signature, &trade_info.user)
@@ -2,6 +2,9 @@ use crate::streaming::event_parser::common::EventMetadata;
use crate::streaming::event_parser::core::account_event_parser::{
NonceAccountEvent, TokenAccountEvent, TokenInfoEvent,
};
use crate::streaming::event_parser::core::common_event_parser::{
SetComputeUnitLimitEvent, SetComputeUnitPriceEvent,
};
use crate::streaming::event_parser::protocols::block::block_meta_event::BlockMetaEvent;
use crate::streaming::event_parser::protocols::bonk::events::*;
use crate::streaming::event_parser::protocols::meteora_damm_v2::events::*;
@@ -83,6 +86,8 @@ pub enum DexEvent {
NonceAccountEvent(NonceAccountEvent),
TokenInfoEvent(TokenInfoEvent),
BlockMetaEvent(BlockMetaEvent),
SetComputeUnitLimitEvent(SetComputeUnitLimitEvent),
SetComputeUnitPriceEvent(SetComputeUnitPriceEvent),
}
impl DexEvent {
@@ -140,6 +145,8 @@ impl DexEvent {
DexEvent::NonceAccountEvent(e) => &e.metadata,
DexEvent::TokenInfoEvent(e) => &e.metadata,
DexEvent::BlockMetaEvent(e) => &e.metadata,
DexEvent::SetComputeUnitLimitEvent(e) => &e.metadata,
DexEvent::SetComputeUnitPriceEvent(e) => &e.metadata,
}
}
@@ -197,6 +204,8 @@ impl DexEvent {
DexEvent::NonceAccountEvent(e) => &mut e.metadata,
DexEvent::TokenInfoEvent(e) => &mut e.metadata,
DexEvent::BlockMetaEvent(e) => &mut e.metadata,
DexEvent::SetComputeUnitLimitEvent(e) => &mut e.metadata,
DexEvent::SetComputeUnitPriceEvent(e) => &mut e.metadata,
}
}
}