sol_parser_sdk/logs/
zero_copy_parser.rs

1//! 零拷贝解析器 - 极致性能优化
2
3use solana_sdk::{pubkey::Pubkey, signature::Signature};
4use crate::core::events::*;
5use super::utils::*;
6use memchr::memmem;
7use base64::{Engine as _, engine::general_purpose};
8use super::perf_hints::prefetch_read;
9
10/// 零分配 PumpFun Trade 事件解析(栈缓冲区)
11#[inline(always)]
12pub fn parse_pumpfun_trade(
13    log: &str,
14    signature: Signature,
15    slot: u64,
16    tx_index: u64,
17    block_time: Option<i64>,
18    grpc_recv_us: i64,
19    is_created_buy: bool,
20) -> Option<DexEvent> {
21    // 使用栈缓冲区,避免堆分配(需要足够大以容纳完整的事件数据)
22    // PumpFun Trade 事件最大约 350 base64 字符 = 262字节,留出余量用 512 字节
23    const MAX_DECODE_SIZE: usize = 512;
24    let mut decode_buf: [u8; MAX_DECODE_SIZE] = [0u8; MAX_DECODE_SIZE];
25
26    // SIMD 快速查找 "Program data: "
27    let log_bytes = log.as_bytes();
28    let pos = memmem::find(log_bytes, b"Program data: ")?;
29    let data_part = log[pos + 14..].trim();
30
31    // 快速检查 discriminator(需要至少12个base64字符才能解码出8字节)
32    // base64: 每4个字符 = 3个字节,所以12个字符 = 9个字节
33    if data_part.len() < 12 {
34        return None;
35    }
36
37    // 解码 discriminator 到栈缓冲区(12个字符解码为9字节,包含完整8字节discriminator)
38    let disc_decoded_len = general_purpose::STANDARD
39        .decode_slice(&data_part.as_bytes()[..12], &mut decode_buf[..9])
40        .ok()?;
41
42    if disc_decoded_len < 8 {
43        return None;
44    }
45
46    // 检查是否为 Trade 事件 discriminator
47    const TRADE_DISCRIMINATOR: [u8; 8] = [189, 219, 127, 211, 78, 230, 97, 238];
48
49    if decode_buf[..8] != TRADE_DISCRIMINATOR {
50        return None;
51    }
52
53    // 完整解码事件数据到栈缓冲区
54    let decoded_len = general_purpose::STANDARD
55        .decode_slice(data_part.as_bytes(), &mut decode_buf)
56        .ok()?;
57
58    if decoded_len < 96 {
59        return None;
60    }
61
62    let data = &decode_buf[8..decoded_len];
63    let mut offset = 0;
64
65    // 预取后续数据到 CPU 缓存
66    unsafe {
67        if data.len() >= 64 {
68            prefetch_read(data.as_ptr().add(32));
69        }
70    }
71
72    // 快速解析字段(内联读取,避免函数调用开销)
73    let mint = read_pubkey_inline(data, offset)?;
74    offset += 32;
75
76    let sol_amount = read_u64_le_inline(data, offset)?;
77    offset += 8;
78
79    let token_amount = read_u64_le_inline(data, offset)?;
80    offset += 8;
81
82    let is_buy = read_u8_inline(data, offset)?;
83    offset += 1;
84
85    let user = read_pubkey_inline(data, offset)?;
86    offset += 32;
87
88    let timestamp = read_i64_le_inline(data, offset)?;
89    offset += 8;
90
91    let virtual_sol_reserves = read_u64_le_inline(data, offset)?;
92    offset += 8;
93
94    let virtual_token_reserves = read_u64_le_inline(data, offset)?;
95    offset += 8;
96
97    let real_sol_reserves = read_u64_le_inline(data, offset).unwrap_or(0);
98    offset += 8;
99
100    let real_token_reserves = read_u64_le_inline(data, offset).unwrap_or(0);
101    offset += 8;
102
103    let fee_recipient = read_pubkey_inline(data, offset).unwrap_or_default();
104    offset += 32;
105
106    let fee_basis_points = read_u64_le_inline(data, offset).unwrap_or(0);
107    offset += 8;
108
109    let fee = read_u64_le_inline(data, offset).unwrap_or(0);
110    offset += 8;
111
112    let creator = read_pubkey_inline(data, offset).unwrap_or_default();
113    offset += 32;
114
115    let creator_fee_basis_points = read_u64_le_inline(data, offset).unwrap_or(0);
116    offset += 8;
117
118    let creator_fee = read_u64_le_inline(data, offset).unwrap_or(0);
119    offset += 8;
120
121    let track_volume = read_u8_inline(data, offset).unwrap_or(0) != 0;
122    offset += 1;
123
124    let total_unclaimed_tokens = read_u64_le_inline(data, offset).unwrap_or(0);
125    offset += 8;
126
127    let total_claimed_tokens = read_u64_le_inline(data, offset).unwrap_or(0);
128    offset += 8;
129
130    let current_sol_volume = read_u64_le_inline(data, offset).unwrap_or(0);
131
132    let metadata = EventMetadata {
133        signature,
134        slot,
135        tx_index,
136        block_time_us: block_time.unwrap_or(0) * 1_000_000,
137        grpc_recv_us,
138    };
139
140    Some(DexEvent::PumpFunTrade(PumpFunTradeEvent {
141        metadata,
142        mint,
143        sol_amount,
144        token_amount,
145        is_buy: is_buy != 0,
146        is_created_buy,
147        user,
148        timestamp,
149        virtual_sol_reserves,
150        virtual_token_reserves,
151        real_sol_reserves,
152        real_token_reserves,
153        fee_recipient,
154        fee_basis_points,
155        fee,
156        creator,
157        creator_fee_basis_points,
158        creator_fee,
159        track_volume,
160        total_unclaimed_tokens,
161        total_claimed_tokens,
162        current_sol_volume,
163        last_update_timestamp: timestamp,
164        // 暂时注释,以后会用,AI禁止改动
165        // bonding_curve: Pubkey::default(),
166        // associated_bonding_curve: Pubkey::default(),
167        // associated_user: Pubkey::default(),
168        // global: Pubkey::default(),
169    }))
170}
171
172/// 内联读取 Pubkey(避免函数调用)
173#[inline(always)]
174fn read_pubkey_inline(data: &[u8], offset: usize) -> Option<Pubkey> {
175    if offset + 32 <= data.len() {
176        let mut bytes = [0u8; 32];
177        bytes.copy_from_slice(&data[offset..offset + 32]);
178        Some(Pubkey::new_from_array(bytes))
179    } else {
180        None
181    }
182}
183
184/// 内联读取 u64(避免函数调用)
185#[inline(always)]
186fn read_u64_le_inline(data: &[u8], offset: usize) -> Option<u64> {
187    if offset + 8 <= data.len() {
188        let mut bytes = [0u8; 8];
189        bytes.copy_from_slice(&data[offset..offset + 8]);
190        Some(u64::from_le_bytes(bytes))
191    } else {
192        None
193    }
194}
195
196/// 内联读取 i64(避免函数调用)
197#[inline(always)]
198fn read_i64_le_inline(data: &[u8], offset: usize) -> Option<i64> {
199    if offset + 8 <= data.len() {
200        let mut bytes = [0u8; 8];
201        bytes.copy_from_slice(&data[offset..offset + 8]);
202        Some(i64::from_le_bytes(bytes))
203    } else {
204        None
205    }
206}
207
208/// 内联读取 u8(避免函数调用)
209#[inline(always)]
210fn read_u8_inline(data: &[u8], offset: usize) -> Option<u8> {
211    data.get(offset).copied()
212}