cherry_ingest/
svm.rs

1use anyhow::{anyhow, Context, Result};
2use serde::{Deserialize, Serialize};
3
4#[derive(Default, Debug, Clone)]
5#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
6pub struct Query {
7    pub from_block: u64,
8    pub to_block: Option<u64>,
9    pub include_all_blocks: bool,
10    pub fields: Fields,
11    pub instructions: Vec<InstructionRequest>,
12    pub transactions: Vec<TransactionRequest>,
13    pub logs: Vec<LogRequest>,
14    pub balances: Vec<BalanceRequest>,
15    pub token_balances: Vec<TokenBalanceRequest>,
16    pub rewards: Vec<RewardRequest>,
17}
18
19#[derive(Debug, Clone, Copy)]
20pub struct Address(pub [u8; 32]);
21
22#[derive(Debug, Clone)]
23pub struct Data(pub Vec<u8>);
24
25#[derive(Debug, Clone, Copy)]
26pub struct D1(pub [u8; 1]);
27
28#[derive(Debug, Clone, Copy)]
29pub struct D2(pub [u8; 2]);
30
31#[derive(Debug, Clone, Copy)]
32pub struct D3(pub [u8; 3]);
33
34#[derive(Debug, Clone, Copy)]
35pub struct D4(pub [u8; 4]);
36
37#[derive(Debug, Clone, Copy)]
38pub struct D8(pub [u8; 8]);
39
40#[cfg(feature = "pyo3")]
41fn extract_base58<const N: usize>(ob: &pyo3::Bound<'_, pyo3::PyAny>) -> pyo3::PyResult<[u8; N]> {
42    use pyo3::types::PyAnyMethods;
43
44    let s: &str = ob.extract()?;
45    let mut out = [0; N];
46
47    bs58::decode(s)
48        .with_alphabet(bs58::Alphabet::BITCOIN)
49        .onto(&mut out)
50        .context("decode base58")?;
51
52    Ok(out)
53}
54
55#[cfg(feature = "pyo3")]
56fn extract_data<const N: usize>(ob: &pyo3::Bound<'_, pyo3::PyAny>) -> pyo3::PyResult<[u8; N]> {
57    use pyo3::types::PyAnyMethods;
58    use pyo3::types::PyTypeMethods;
59
60    let ob_type: String = ob.get_type().name()?.to_string();
61    match ob_type.as_str() {
62        "str" => {
63            let s: &str = ob.extract()?;
64            let out = hex_to_bytes(s).context("failed to decode hex")?;
65            if out.len() != N {
66                return Err(anyhow!("expected length {}, got {}", N, out.len()).into());
67            }
68            let out: [u8; N] = out
69                .try_into()
70                .map_err(|e| anyhow!("failed to convert to array: {:?}", e))?;
71            Ok(out)
72        }
73        "bytes" => {
74            let out: Vec<u8> = ob.extract()?;
75            if out.len() != N {
76                return Err(anyhow!("expected length {}, got {}", N, out.len()).into());
77            }
78            let out: [u8; N] = out
79                .try_into()
80                .map_err(|e| anyhow!("failed to convert to array: {:?}", e))?;
81            Ok(out)
82        }
83        _ => Err(anyhow!("unknown type: {}", ob_type).into()),
84    }
85}
86
87fn hex_to_bytes(hex_string: &str) -> Result<Vec<u8>> {
88    let hex_string = hex_string.strip_prefix("0x").unwrap_or(hex_string);
89    let hex_string = if hex_string.len() % 2 == 1 {
90        format!("0{}", hex_string)
91    } else {
92        hex_string.to_string()
93    };
94    let out = (0..hex_string.len())
95        .step_by(2)
96        .map(|i| {
97            u8::from_str_radix(&hex_string[i..i + 2], 16)
98                .context("failed to parse hexstring to bytes")
99        })
100        .collect::<Result<Vec<_>, _>>()?;
101
102    Ok(out)
103}
104
105#[cfg(feature = "pyo3")]
106impl<'py> pyo3::FromPyObject<'py> for Address {
107    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
108        let out = extract_base58(ob)?;
109        Ok(Self(out))
110    }
111}
112
113#[cfg(feature = "pyo3")]
114impl<'py> pyo3::FromPyObject<'py> for Data {
115    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
116        use pyo3::types::PyAnyMethods;
117        use pyo3::types::PyTypeMethods;
118
119        let ob_type: String = ob.get_type().name()?.to_string();
120        match ob_type.as_str() {
121            "str" => {
122                let s: &str = ob.extract()?;
123                let out = hex_to_bytes(s).context("failed to decode hex")?;
124                Ok(Self(out))
125            }
126            "bytes" => {
127                let out: Vec<u8> = ob.extract()?;
128                Ok(Self(out))
129            }
130            _ => Err(anyhow!("unknown type: {}", ob_type).into()),
131        }
132    }
133}
134
135#[cfg(feature = "pyo3")]
136impl<'py> pyo3::FromPyObject<'py> for D1 {
137    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
138        let out = extract_data(ob)?;
139        Ok(Self(out))
140    }
141}
142
143#[cfg(feature = "pyo3")]
144impl<'py> pyo3::FromPyObject<'py> for D2 {
145    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
146        let out = extract_data(ob)?;
147        Ok(Self(out))
148    }
149}
150
151#[cfg(feature = "pyo3")]
152impl<'py> pyo3::FromPyObject<'py> for D3 {
153    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
154        let out = extract_data(ob)?;
155        Ok(Self(out))
156    }
157}
158
159#[cfg(feature = "pyo3")]
160impl<'py> pyo3::FromPyObject<'py> for D4 {
161    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
162        let out = extract_data(ob)?;
163        Ok(Self(out))
164    }
165}
166
167#[cfg(feature = "pyo3")]
168impl<'py> pyo3::FromPyObject<'py> for D8 {
169    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
170        let out = extract_data(ob)?;
171        Ok(Self(out))
172    }
173}
174
175#[derive(Default, Debug, Clone)]
176#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
177pub struct InstructionRequest {
178    pub program_id: Vec<Address>,
179    pub discriminator: Vec<Data>,
180    pub d1: Vec<D1>,
181    pub d2: Vec<D2>,
182    pub d3: Vec<D3>,
183    pub d4: Vec<D4>,
184    pub d8: Vec<D8>,
185    pub a0: Vec<Address>,
186    pub a1: Vec<Address>,
187    pub a2: Vec<Address>,
188    pub a3: Vec<Address>,
189    pub a4: Vec<Address>,
190    pub a5: Vec<Address>,
191    pub a6: Vec<Address>,
192    pub a7: Vec<Address>,
193    pub a8: Vec<Address>,
194    pub a9: Vec<Address>,
195    pub is_committed: bool,
196    pub include_transactions: bool,
197    pub include_transaction_token_balances: bool,
198    pub include_logs: bool,
199    pub include_inner_instructions: bool,
200    pub include_blocks: bool,
201}
202
203#[derive(Default, Debug, Clone)]
204#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
205pub struct TransactionRequest {
206    pub fee_payer: Vec<Address>,
207    pub include_instructions: bool,
208    pub include_logs: bool,
209    pub include_blocks: bool,
210}
211
212#[derive(Default, Debug, Clone)]
213#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
214pub struct LogRequest {
215    pub program_id: Vec<Address>,
216    pub kind: Vec<LogKind>,
217    pub include_transactions: bool,
218    pub include_instructions: bool,
219    pub include_blocks: bool,
220}
221
222#[derive(Debug, Clone, Copy)]
223pub enum LogKind {
224    Log,
225    Data,
226    Other,
227}
228
229impl LogKind {
230    pub fn as_str(&self) -> &str {
231        match self {
232            Self::Log => "log",
233            Self::Data => "data",
234            Self::Other => "other",
235        }
236    }
237
238    pub fn from_str(s: &str) -> Result<Self> {
239        match s {
240            "log" => Ok(Self::Log),
241            "data" => Ok(Self::Data),
242            "other" => Ok(Self::Other),
243            _ => Err(anyhow!("unknown log kind: {}", s)),
244        }
245    }
246}
247
248#[cfg(feature = "pyo3")]
249impl<'py> pyo3::FromPyObject<'py> for LogKind {
250    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
251        use pyo3::types::PyAnyMethods;
252
253        let s: &str = ob.extract().context("extract string")?;
254
255        Ok(Self::from_str(s).context("from str")?)
256    }
257}
258
259#[derive(Default, Debug, Clone)]
260#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
261pub struct BalanceRequest {
262    pub account: Vec<Address>,
263    pub include_transactions: bool,
264    pub include_transaction_instructions: bool,
265    pub include_blocks: bool,
266}
267
268#[derive(Default, Debug, Clone)]
269#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
270pub struct TokenBalanceRequest {
271    pub account: Vec<Address>,
272    pub pre_program_id: Vec<Address>,
273    pub post_program_id: Vec<Address>,
274    pub pre_mint: Vec<Address>,
275    pub post_mint: Vec<Address>,
276    pub pre_owner: Vec<Address>,
277    pub post_owner: Vec<Address>,
278    pub include_transactions: bool,
279    pub include_transaction_instructions: bool,
280    pub include_blocks: bool,
281}
282
283#[derive(Default, Debug, Clone)]
284#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
285pub struct RewardRequest {
286    pub pubkey: Vec<Address>,
287    pub include_blocks: bool,
288}
289
290#[derive(Deserialize, Serialize, Default, Debug, Clone, Copy)]
291#[serde(default)]
292#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
293pub struct Fields {
294    pub instruction: InstructionFields,
295    pub transaction: TransactionFields,
296    pub log: LogFields,
297    pub balance: BalanceFields,
298    pub token_balance: TokenBalanceFields,
299    pub reward: RewardFields,
300    pub block: BlockFields,
301}
302
303impl Fields {
304    pub fn all() -> Self {
305        Self {
306            instruction: InstructionFields::all(),
307            transaction: TransactionFields::all(),
308            log: LogFields::all(),
309            balance: BalanceFields::all(),
310            token_balance: TokenBalanceFields::all(),
311            reward: RewardFields::all(),
312            block: BlockFields::all(),
313        }
314    }
315}
316
317#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
318#[serde(default)]
319#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
320pub struct InstructionFields {
321    pub block_slot: bool,
322    pub block_hash: bool,
323    pub transaction_index: bool,
324    pub instruction_address: bool,
325    pub program_id: bool,
326    pub a0: bool,
327    pub a1: bool,
328    pub a2: bool,
329    pub a3: bool,
330    pub a4: bool,
331    pub a5: bool,
332    pub a6: bool,
333    pub a7: bool,
334    pub a8: bool,
335    pub a9: bool,
336    pub rest_of_accounts: bool,
337    pub data: bool,
338    pub d1: bool,
339    pub d2: bool,
340    pub d4: bool,
341    pub d8: bool,
342    pub error: bool,
343    pub compute_units_consumed: bool,
344    pub is_committed: bool,
345    pub has_dropped_log_messages: bool,
346}
347
348impl InstructionFields {
349    pub fn all() -> Self {
350        InstructionFields {
351            block_slot: true,
352            block_hash: true,
353            transaction_index: true,
354            instruction_address: true,
355            program_id: true,
356            a0: true,
357            a1: true,
358            a2: true,
359            a3: true,
360            a4: true,
361            a5: true,
362            a6: true,
363            a7: true,
364            a8: true,
365            a9: true,
366            rest_of_accounts: true,
367            data: true,
368            d1: true,
369            d2: true,
370            d4: true,
371            d8: true,
372            error: true,
373            compute_units_consumed: true,
374            is_committed: true,
375            has_dropped_log_messages: true,
376        }
377    }
378}
379
380#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
381#[serde(default)]
382#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
383pub struct TransactionFields {
384    pub block_slot: bool,
385    pub block_hash: bool,
386    pub transaction_index: bool,
387    pub signature: bool,
388    pub version: bool,
389    pub account_keys: bool,
390    pub address_table_lookups: bool,
391    pub num_readonly_signed_accounts: bool,
392    pub num_readonly_unsigned_accounts: bool,
393    pub num_required_signatures: bool,
394    pub recent_blockhash: bool,
395    pub signatures: bool,
396    pub err: bool,
397    pub fee: bool,
398    pub compute_units_consumed: bool,
399    pub loaded_readonly_addresses: bool,
400    pub loaded_writable_addresses: bool,
401    pub fee_payer: bool,
402    pub has_dropped_log_messages: bool,
403}
404
405impl TransactionFields {
406    pub fn all() -> Self {
407        TransactionFields {
408            block_slot: true,
409            block_hash: true,
410            transaction_index: true,
411            signature: true,
412            version: true,
413            account_keys: true,
414            address_table_lookups: true,
415            num_readonly_signed_accounts: true,
416            num_readonly_unsigned_accounts: true,
417            num_required_signatures: true,
418            recent_blockhash: true,
419            signatures: true,
420            err: true,
421            fee: true,
422            compute_units_consumed: true,
423            loaded_readonly_addresses: true,
424            loaded_writable_addresses: true,
425            fee_payer: true,
426            has_dropped_log_messages: true,
427        }
428    }
429}
430
431#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
432#[serde(default)]
433#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
434pub struct LogFields {
435    pub block_slot: bool,
436    pub block_hash: bool,
437    pub transaction_index: bool,
438    pub log_index: bool,
439    pub instruction_address: bool,
440    pub program_id: bool,
441    pub kind: bool,
442    pub message: bool,
443}
444
445impl LogFields {
446    pub fn all() -> Self {
447        LogFields {
448            block_slot: true,
449            block_hash: true,
450            transaction_index: true,
451            log_index: true,
452            instruction_address: true,
453            program_id: true,
454            kind: true,
455            message: true,
456        }
457    }
458}
459
460#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
461#[serde(default)]
462#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
463pub struct BalanceFields {
464    pub block_slot: bool,
465    pub block_hash: bool,
466    pub transaction_index: bool,
467    pub account: bool,
468    pub pre: bool,
469    pub post: bool,
470}
471
472impl BalanceFields {
473    pub fn all() -> Self {
474        BalanceFields {
475            block_slot: true,
476            block_hash: true,
477            transaction_index: true,
478            account: true,
479            pre: true,
480            post: true,
481        }
482    }
483}
484
485#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
486#[serde(default)]
487#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
488pub struct TokenBalanceFields {
489    pub block_slot: bool,
490    pub block_hash: bool,
491    pub transaction_index: bool,
492    pub account: bool,
493    pub pre_mint: bool,
494    pub post_mint: bool,
495    pub pre_decimals: bool,
496    pub post_decimals: bool,
497    pub pre_program_id: bool,
498    pub post_program_id: bool,
499    pub pre_owner: bool,
500    pub post_owner: bool,
501    pub pre_amount: bool,
502    pub post_amount: bool,
503}
504
505impl TokenBalanceFields {
506    pub fn all() -> Self {
507        TokenBalanceFields {
508            block_slot: true,
509            block_hash: true,
510            transaction_index: true,
511            account: true,
512            pre_mint: true,
513            post_mint: true,
514            pre_decimals: true,
515            post_decimals: true,
516            pre_program_id: true,
517            post_program_id: true,
518            pre_owner: true,
519            post_owner: true,
520            pre_amount: true,
521            post_amount: true,
522        }
523    }
524}
525
526#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
527#[serde(default)]
528#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
529pub struct RewardFields {
530    pub block_slot: bool,
531    pub block_hash: bool,
532    pub pubkey: bool,
533    pub lamports: bool,
534    pub post_balance: bool,
535    pub reward_type: bool,
536    pub commission: bool,
537}
538
539impl RewardFields {
540    pub fn all() -> Self {
541        RewardFields {
542            block_slot: true,
543            block_hash: true,
544            pubkey: true,
545            lamports: true,
546            post_balance: true,
547            reward_type: true,
548            commission: true,
549        }
550    }
551}
552
553#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
554#[serde(default)]
555#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
556pub struct BlockFields {
557    pub slot: bool,
558    pub hash: bool,
559    pub parent_slot: bool,
560    pub parent_hash: bool,
561    pub height: bool,
562    pub timestamp: bool,
563}
564
565impl BlockFields {
566    pub fn all() -> Self {
567        BlockFields {
568            slot: true,
569            hash: true,
570            parent_slot: true,
571            parent_hash: true,
572            height: true,
573            timestamp: true,
574        }
575    }
576}