Skip to main content

sol_parser_sdk/logs/
utils.rs

1//! 日志解析通用工具函数
2//!
3//! 提供字节数据解析的基础工具,不使用 BorshDeserialize
4
5use crate::core::clock::now_us;
6use crate::core::events::EventMetadata;
7#[cfg(target_os = "windows")]
8use crate::core::now_micros;
9use base64::{engine::general_purpose, Engine as _};
10use solana_sdk::{pubkey::Pubkey, signature::Signature};
11
12/// 从日志中提取程序数据(使用 SIMD 优化查找)
13#[inline]
14pub fn extract_program_data(log: &str) -> Option<Vec<u8>> {
15    use memchr::memmem;
16
17    let log_bytes = log.as_bytes();
18    let pos = memmem::find(log_bytes, b"Program data: ")?;
19
20    let data_part = &log[pos + 14..];
21    general_purpose::STANDARD.decode(data_part.trim()).ok()
22}
23
24/// Decode only the first 12 Base64 bytes needed for an 8-byte discriminator.
25#[inline]
26pub fn extract_discriminator_fast(log: &str) -> Option<[u8; 8]> {
27    let pos = memchr::memmem::find(log.as_bytes(), b"Program data: ")?;
28    let prefix = log.get(pos + 14..)?.trim().as_bytes().get(..12)?;
29    let mut bytes = [0u8; 9];
30    let len = general_purpose::STANDARD.decode_slice(prefix, &mut bytes).ok()?;
31    (len >= 8).then(|| bytes[..8].try_into().unwrap())
32}
33
34/// 从字节数组中读取 u64(小端序)- SIMD 优化
35#[inline]
36pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
37    data.get(offset..offset.checked_add(8)?).map(|slice| u64::from_le_bytes(slice.try_into().unwrap()))
38}
39
40/// 从字节数组中读取 u32(小端序)- SIMD 优化
41#[inline]
42pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
43    data.get(offset..offset.checked_add(4)?).map(|slice| u32::from_le_bytes(slice.try_into().unwrap()))
44}
45
46/// 从字节数组中读取 i64(小端序)- SIMD 优化
47pub fn read_i64_le(data: &[u8], offset: usize) -> Option<i64> {
48    data.get(offset..offset.checked_add(8)?).map(|slice| i64::from_le_bytes(slice.try_into().unwrap()))
49}
50
51/// 从字节数组中读取 i32(小端序)- SIMD 优化
52pub fn read_i32_le(data: &[u8], offset: usize) -> Option<i32> {
53    data.get(offset..offset.checked_add(4)?).map(|slice| i32::from_le_bytes(slice.try_into().unwrap()))
54}
55
56/// 从字节数组中读取 u128(小端序)- SIMD 优化
57pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
58    data.get(offset..offset.checked_add(16)?).map(|slice| u128::from_le_bytes(slice.try_into().unwrap()))
59}
60
61/// 从字节数组中读取 u16(小端序)- SIMD 优化
62pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
63    data.get(offset..offset.checked_add(2)?).map(|slice| u16::from_le_bytes(slice.try_into().unwrap()))
64}
65
66/// 从字节数组中读取 u8
67pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
68    data.get(offset).copied()
69}
70
71/// 从字节数组中读取 Pubkey(32字节)- SIMD 优化
72#[inline]
73pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
74    data.get(offset..offset.checked_add(32)?).and_then(|slice| {
75        let key_bytes: [u8; 32] = slice.try_into().ok()?;
76        Some(Pubkey::new_from_array(key_bytes))
77    })
78}
79
80/// 从字节数组中读取字符串(分配版本,向后兼容)
81pub fn read_string(data: &[u8], offset: usize) -> Option<(String, usize)> {
82    let (string_ref, consumed) = read_string_ref(data, offset)?;
83    Some((string_ref.to_string(), consumed))
84}
85
86/// 从字节数组中读取字符串引用(零拷贝版本)
87///
88/// ## 零延迟优化
89/// 返回 &str 引用而不是 String,避免 50-100ns 的堆分配开销
90///
91/// ## 用法
92/// ```ignore
93/// let (name_ref, consumed) = read_string_ref(data, offset)?;
94/// // 直接使用引用,无需分配
95/// println!("Name: {}", name_ref);
96/// ```
97#[inline(always)] // 零延迟优化:内联热路径
98pub fn read_string_ref(data: &[u8], offset: usize) -> Option<(&str, usize)> {
99    let tail = data.get(offset..)?;
100    let len = read_u32_le(tail, 0)? as usize;
101    let bytes = tail.get(4..)?.get(..len)?;
102    Some((std::str::from_utf8(bytes).ok()?, len.checked_add(4)?))
103}
104
105/// 读取布尔值
106pub fn read_bool(data: &[u8], offset: usize) -> Option<bool> {
107    if data.len() <= offset {
108        return None;
109    }
110    Some(data[offset] == 1)
111}
112
113/// 将 timestamp (seconds, nanos) 转换为微秒表示
114pub fn timestamp_to_microseconds(seconds: i64, nanos: i32) -> i128 {
115    // 用 i128 避免溢出
116    seconds as i128 * 1_000_000 + (nanos as i128 / 1_000)
117}
118
119/// 创建事件元数据的通用函数
120pub fn create_metadata_simple(
121    signature: Signature,
122    slot: u64,
123    tx_index: u64,
124    block_time_us: Option<i64>,
125    program_id: Pubkey,
126    grpc_recv_us: i64,
127) -> EventMetadata {
128    EventMetadata {
129        signature,
130        slot,
131        tx_index,
132        block_time_us: block_time_us.unwrap_or(0),
133        grpc_recv_us,
134        recent_blockhash: None,
135    }
136}
137
138/// 创建默认事件元数据的通用函数(不需要程序ID)
139pub fn create_metadata_default(
140    signature: Signature,
141    slot: u64,
142    tx_index: u64,
143    block_time_us: Option<i64>,
144) -> EventMetadata {
145    let current_time = now_us();
146    EventMetadata {
147        signature,
148        slot,
149        tx_index,
150        block_time_us: block_time_us.unwrap_or(0),
151        grpc_recv_us: current_time,
152        recent_blockhash: None,
153    }
154}
155
156/// 文本回退解析工具
157pub mod text_parser {
158
159    /// 从文本中提取数字
160    pub fn extract_number_from_text(text: &str, field: &str) -> Option<u64> {
161        if let Some(start) = text.find(&format!("{}:", field)) {
162            let after_colon = &text[start + field.len() + 1..];
163            if let Some(end) = after_colon.find(' ').or_else(|| after_colon.find(',')) {
164                after_colon[..end].trim().parse().ok()
165            } else {
166                after_colon.trim().parse().ok()
167            }
168        } else {
169            None
170        }
171    }
172
173    /// 从文本中提取字段值(分配版本,向后兼容)
174    pub fn extract_text_field(text: &str, field: &str) -> Option<String> {
175        extract_text_field_ref(text, field).map(|s| s.to_string())
176    }
177
178    /// 从文本中提取字段值引用(零拷贝版本)
179    ///
180    /// ## 零延迟优化
181    /// 返回 &str 引用而不是 String,避免 50-100ns 的堆分配开销
182    ///
183    /// ## 用法
184    /// ```ignore
185    /// let value_ref = extract_text_field_ref(log, "amount")?;
186    /// let amount: u64 = value_ref.parse().ok()?;
187    /// ```
188    #[inline(always)] // 零延迟优化:内联热路径
189    pub fn extract_text_field_ref<'a>(text: &'a str, field: &str) -> Option<&'a str> {
190        let start = text.find(&format!("{}:", field))?;
191        let after_colon = &text[start + field.len() + 1..];
192        if let Some(end) = after_colon.find(',').or_else(|| after_colon.find(' ')) {
193            Some(after_colon[..end].trim())
194        } else {
195            Some(after_colon.trim())
196        }
197    }
198
199    /// 检测交易类型
200    pub fn detect_trade_type(log: &str) -> Option<bool> {
201        if log.contains("buy") || log.contains("Buy") {
202            Some(true)
203        } else if log.contains("sell") || log.contains("Sell") {
204            Some(false)
205        } else {
206            None
207        }
208    }
209}
210
211#[cfg(test)]
212mod review_log_bounds_tests {
213    use super::*;
214
215    #[test]
216    fn discriminator_prefix_handles_short_and_non_ascii_logs_without_panicking() {
217        let discriminator = [1u8, 2, 3, 4, 5, 6, 7, 8];
218        let encoded = general_purpose::STANDARD.encode(discriminator);
219        assert_eq!(encoded.len(), 12);
220        assert_eq!(extract_discriminator_fast(&format!("Program data: {encoded}")), Some(discriminator));
221        for length in 0..encoded.len() {
222            assert_eq!(extract_discriminator_fast(&format!("Program data: {}", &encoded[..length])), None);
223        }
224        for prefix in ["AAAAAAAAAAAA", "AAAAAAAAAAAAA", "AAAAAAAAAAAAAA", "AAAAAAAAAAAAAAA", "AAAAAAAAAAAAAAAé", "éééééééé"] {
225            let log = format!("Program data: {prefix}");
226            let _ = extract_discriminator_fast(&log);
227        }
228        let mut payload = discriminator.to_vec();
229        payload.extend_from_slice(&[9; 128]);
230        assert_eq!(extract_discriminator_fast(&format!("Program data: {}", general_purpose::STANDARD.encode(payload))), Some(discriminator));
231    }
232
233    #[test]
234    fn log_readers_reject_overflowing_offsets_and_invalid_strings() {
235        let data = [0u8; 64];
236        for offset in [65, usize::MAX - 32, usize::MAX - 1, usize::MAX] {
237            assert_eq!(read_u16_le(&data, offset), None);
238            assert_eq!(read_u32_le(&data, offset), None);
239            assert_eq!(read_i32_le(&data, offset), None);
240            assert_eq!(read_u64_le(&data, offset), None);
241            assert_eq!(read_i64_le(&data, offset), None);
242            assert_eq!(read_u128_le(&data, offset), None);
243            assert_eq!(read_pubkey(&data, offset), None);
244            assert_eq!(read_string_ref(&data, offset), None);
245        }
246        assert_eq!(read_string_ref(&u32::MAX.to_le_bytes(), 0), None);
247        assert_eq!(read_string_ref(&[1, 0, 0, 0, 255], 0), None);
248    }
249}