From 4b019e5e012e9b67d6f8592b28445cd73008d14c Mon Sep 17 00:00:00 2001 From: ysq Date: Sun, 16 Nov 2025 22:49:37 +0800 Subject: [PATCH] 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. --- Cargo.toml | 2 +- README.md | 4 +- README_CN.md | 4 +- src/streaming/event_parser/common/types.rs | 4 + .../event_parser/core/common_event_parser.rs | 65 +++++++++++ src/streaming/event_parser/core/dispatcher.rs | 23 ++++ .../event_parser/core/event_parser.rs | 102 ++++++++++++------ src/streaming/event_parser/core/traits.rs | 9 ++ 8 files changed, 174 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 93b228d..847a40c 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-streamer-sdk" -version = "1.1.2" +version = "1.1.3" edition = "2021" authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/solana-streamer" diff --git a/README.md b/README.md index ffebcc4..e9714ab 100755 --- a/README.md +++ b/README.md @@ -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 diff --git a/README_CN.md b/README_CN.md index 8c1c01e..1d96957 100644 --- a/README_CN.md +++ b/README_CN.md @@ -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" ``` ## 🔄 迁移指南 diff --git a/src/streaming/event_parser/common/types.rs b/src/streaming/event_parser/common/types.rs index 91fd51d..7bcaa96 100755 --- a/src/streaming/event_parser/common/types.rs +++ b/src/streaming/event_parser/common/types.rs @@ -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"), } } diff --git a/src/streaming/event_parser/core/common_event_parser.rs b/src/streaming/event_parser/core/common_event_parser.rs index 6f34fd3..948676b 100644 --- a/src/streaming/event_parser/core/common_event_parser.rs +++ b/src/streaming/event_parser/core/common_event_parser.rs @@ -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 { + 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, + } + } } diff --git a/src/streaming/event_parser/core/dispatcher.rs b/src/streaming/event_parser/core/dispatcher.rs index 6429f3a..77691ce 100644 --- a/src/streaming/event_parser/core/dispatcher.rs +++ b/src/streaming/event_parser/core/dispatcher.rs @@ -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 { + CommonEventParser::parse_compute_budget_instruction(instruction_data, metadata) + } + /// 获取指定协议的 program_id #[inline] pub fn get_program_id(protocol: Protocol) -> Pubkey { diff --git a/src/streaming/event_parser/core/event_parser.rs b/src/streaming/event_parser/core/event_parser.rs index 0da6ee1..93a5319 100644 --- a/src/streaming/event_parser/core/event_parser.rs +++ b/src/streaming/event_parser/core/event_parser.rs @@ -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 = 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 = 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 = 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 = 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) diff --git a/src/streaming/event_parser/core/traits.rs b/src/streaming/event_parser/core/traits.rs index 6fd4c7f..0dc95f8 100755 --- a/src/streaming/event_parser/core/traits.rs +++ b/src/streaming/event_parser/core/traits.rs @@ -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, } } }