light_program_test/program_test/
rpc.rs

1use std::{fmt::Debug, marker::Send};
2
3use anchor_lang::pubkey;
4use async_trait::async_trait;
5use borsh::BorshDeserialize;
6use light_client::{
7    indexer::{Indexer, TreeInfo},
8    rpc::{LightClientConfig, Rpc, RpcError},
9};
10use light_compressed_account::TreeType;
11use light_event::{
12    error::ParseIndexerEventError,
13    event::{BatchPublicTransactionEvent, PublicTransactionEvent},
14    parse::event_from_light_transaction,
15};
16use solana_rpc_client_api::config::RpcSendTransactionConfig;
17use solana_sdk::{
18    account::Account,
19    address_lookup_table::AddressLookupTableAccount,
20    clock::{Clock, Slot},
21    hash::Hash,
22    instruction::Instruction,
23    pubkey::Pubkey,
24    rent::Rent,
25    signature::{Keypair, Signature},
26    transaction::Transaction,
27};
28use solana_transaction_status_client_types::TransactionStatus;
29
30use crate::{
31    indexer::{TestIndexer, TestIndexerExtensions},
32    litesvm_extensions::LiteSvmExtensions,
33    program_test::LightProgramTest,
34};
35
36#[async_trait]
37impl Rpc for LightProgramTest {
38    async fn new(_config: LightClientConfig) -> Result<Self, RpcError>
39    where
40        Self: Sized,
41    {
42        Err(RpcError::CustomError(
43            "LightProgramTest::new is not supported in program-test context".into(),
44        ))
45    }
46
47    fn get_payer(&self) -> &Keypair {
48        &self.payer
49    }
50
51    fn get_url(&self) -> String {
52        "get_url doesn't make sense for LightProgramTest".to_string()
53    }
54
55    async fn health(&self) -> Result<(), RpcError> {
56        Ok(())
57    }
58
59    async fn get_program_accounts(
60        &self,
61        program_id: &Pubkey,
62    ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
63        Ok(self.context.get_program_accounts(program_id))
64    }
65
66    async fn get_program_accounts_with_discriminator(
67        &self,
68        program_id: &Pubkey,
69        discriminator: &[u8],
70    ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
71        let all_accounts = self.context.get_program_accounts(program_id);
72        Ok(all_accounts
73            .into_iter()
74            .filter(|(_, account)| {
75                account.data.len() >= discriminator.len()
76                    && &account.data[..discriminator.len()] == discriminator
77            })
78            .collect())
79    }
80
81    async fn confirm_transaction(&self, _transaction: Signature) -> Result<bool, RpcError> {
82        Ok(true)
83    }
84
85    async fn get_account(&self, address: Pubkey) -> Result<Option<Account>, RpcError> {
86        Ok(self.context.get_account(&address))
87    }
88
89    async fn get_multiple_accounts(
90        &self,
91        addresses: &[Pubkey],
92    ) -> Result<Vec<Option<Account>>, RpcError> {
93        Ok(addresses
94            .iter()
95            .map(|address| self.context.get_account(address))
96            .collect())
97    }
98
99    async fn get_minimum_balance_for_rent_exemption(
100        &self,
101        data_len: usize,
102    ) -> Result<u64, RpcError> {
103        let rent = self.context.get_sysvar::<Rent>();
104
105        Ok(rent.minimum_balance(data_len))
106    }
107
108    async fn airdrop_lamports(
109        &mut self,
110        to: &Pubkey,
111        lamports: u64,
112    ) -> Result<Signature, RpcError> {
113        let res = self.context.airdrop(to, lamports).map_err(|e| e.err)?;
114        Ok(res.signature)
115    }
116
117    async fn get_balance(&self, pubkey: &Pubkey) -> Result<u64, RpcError> {
118        Ok(self.context.get_balance(pubkey).unwrap())
119    }
120
121    async fn get_latest_blockhash(&mut self) -> Result<(Hash, u64), RpcError> {
122        let slot = self.get_slot().await?;
123        let hash = self.context.latest_blockhash();
124        Ok((hash, slot))
125    }
126
127    async fn get_slot(&self) -> Result<u64, RpcError> {
128        Ok(self.context.get_sysvar::<Clock>().slot)
129    }
130
131    async fn get_transaction_slot(&self, _signature: &Signature) -> Result<u64, RpcError> {
132        unimplemented!();
133    }
134
135    async fn get_signature_statuses(
136        &self,
137        _signatures: &[Signature],
138    ) -> Result<Vec<Option<TransactionStatus>>, RpcError> {
139        Err(RpcError::CustomError(
140            "get_signature_statuses is unimplemented for LightProgramTest".to_string(),
141        ))
142    }
143
144    async fn send_transaction(&self, _transaction: &Transaction) -> Result<Signature, RpcError> {
145        Err(RpcError::CustomError(
146            "send_transaction is unimplemented for ProgramTestConnection".to_string(),
147        ))
148    }
149
150    async fn send_transaction_with_config(
151        &self,
152        _transaction: &Transaction,
153        _config: RpcSendTransactionConfig,
154    ) -> Result<Signature, RpcError> {
155        Err(RpcError::CustomError(
156            "send_transaction_with_config is unimplemented for ProgramTestConnection".to_string(),
157        ))
158    }
159
160    async fn process_transaction(
161        &mut self,
162        transaction: Transaction,
163    ) -> Result<Signature, RpcError> {
164        let sig = *transaction.signatures.first().unwrap();
165        if self.indexer.is_some() {
166            // Delegate to _send_transaction_with_batched_event which handles counter, logging and pre_context
167            self._send_transaction_with_batched_event(transaction)
168                .await?;
169        } else {
170            // Cache the current context before transaction execution
171            let pre_context_snapshot = self.context.clone();
172
173            // Handle transaction directly without logging (logging should be done elsewhere)
174            self.transaction_counter += 1;
175            let _res = self.context.send_transaction(transaction).map_err(|x| {
176                if self.config.log_failed_tx {
177                    println!("{}", x.meta.pretty_logs());
178                }
179
180                RpcError::TransactionError(x.err)
181            })?;
182
183            self.maybe_print_logs(_res.pretty_logs());
184
185            // Update pre_context only after successful transaction execution
186            self.pre_context = Some(pre_context_snapshot);
187        }
188        Ok(sig)
189    }
190
191    async fn process_transaction_with_context(
192        &mut self,
193        transaction: Transaction,
194    ) -> Result<(Signature, Slot), RpcError> {
195        let sig = *transaction.signatures.first().unwrap();
196
197        // Cache the current context before transaction execution
198        let pre_context_snapshot = self.context.clone();
199
200        self.transaction_counter += 1;
201        let _res = self.context.send_transaction(transaction).map_err(|x| {
202            if self.config.log_failed_tx {
203                println!("{}", x.meta.pretty_logs());
204            }
205            RpcError::TransactionError(x.err)
206        })?;
207
208        let slot = self.context.get_sysvar::<Clock>().slot;
209        self.maybe_print_logs(_res.pretty_logs());
210
211        // Update pre_context only after successful transaction execution
212        self.pre_context = Some(pre_context_snapshot);
213
214        Ok((sig, slot))
215    }
216
217    async fn create_and_send_transaction_with_event<T>(
218        &mut self,
219        instructions: &[Instruction],
220        payer: &Pubkey,
221        signers: &[&Keypair],
222    ) -> Result<Option<(T, Signature, u64)>, RpcError>
223    where
224        T: BorshDeserialize + Send + Debug,
225    {
226        self._create_and_send_transaction_with_event::<T>(instructions, payer, signers)
227            .await
228    }
229
230    async fn create_and_send_transaction_with_batched_event(
231        &mut self,
232        instructions: &[Instruction],
233        payer: &Pubkey,
234        signers: &[&Keypair],
235    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
236        self._create_and_send_transaction_with_batched_event(instructions, payer, signers)
237            .await
238    }
239
240    async fn create_and_send_transaction_with_public_event(
241        &mut self,
242        instruction: &[Instruction],
243        payer: &Pubkey,
244        signers: &[&Keypair],
245    ) -> Result<Option<(PublicTransactionEvent, Signature, Slot)>, RpcError> {
246        let event = self
247            ._create_and_send_transaction_with_batched_event(instruction, payer, signers)
248            .await?;
249        let event = event.map(|e| (e.0[0].event.clone(), e.1, e.2));
250
251        Ok(event)
252    }
253
254    fn indexer(&self) -> Result<&impl Indexer, RpcError> {
255        self.indexer.as_ref().ok_or(RpcError::IndexerNotInitialized)
256    }
257
258    fn indexer_mut(&mut self) -> Result<&mut impl Indexer, RpcError> {
259        self.indexer.as_mut().ok_or(RpcError::IndexerNotInitialized)
260    }
261
262    /// Fetch the latest state tree addresses from the cluster.
263    async fn get_latest_active_state_trees(&mut self) -> Result<Vec<TreeInfo>, RpcError> {
264        #[cfg(not(feature = "v2"))]
265        return Ok(self
266            .test_accounts
267            .v1_state_trees
268            .iter()
269            .copied()
270            .map(|tree| tree.into())
271            .collect());
272        #[cfg(feature = "v2")]
273        return Ok(self
274            .test_accounts
275            .v2_state_trees
276            .iter()
277            .map(|tree| (*tree).into())
278            .collect());
279    }
280
281    /// Fetch the latest state tree addresses from the cluster.
282    fn get_state_tree_infos(&self) -> Vec<TreeInfo> {
283        #[cfg(not(feature = "v2"))]
284        return self
285            .test_accounts
286            .v1_state_trees
287            .iter()
288            .copied()
289            .map(|tree| tree.into())
290            .collect();
291        #[cfg(feature = "v2")]
292        return self
293            .test_accounts
294            .v2_state_trees
295            .iter()
296            .map(|tree| (*tree).into())
297            .collect();
298    }
299
300    /// Gets a random active state tree.
301    /// State trees are cached and have to be fetched or set.
302    fn get_random_state_tree_info(&self) -> Result<TreeInfo, RpcError> {
303        use rand::Rng;
304        let mut rng = rand::thread_rng();
305        #[cfg(not(feature = "v2"))]
306        {
307            if self.test_accounts.v1_state_trees.is_empty() {
308                return Err(RpcError::NoStateTreesAvailable);
309            }
310            Ok(self.test_accounts.v1_state_trees
311                [rng.gen_range(0..self.test_accounts.v1_state_trees.len())]
312            .into())
313        }
314        #[cfg(feature = "v2")]
315        {
316            if self.test_accounts.v2_state_trees.is_empty() {
317                return Err(RpcError::NoStateTreesAvailable);
318            }
319            Ok(self.test_accounts.v2_state_trees
320                [rng.gen_range(0..self.test_accounts.v2_state_trees.len())]
321            .into())
322        }
323    }
324
325    /// Gets a random v1 state tree.
326    /// State trees are cached and have to be fetched or set.
327    fn get_random_state_tree_info_v1(&self) -> Result<TreeInfo, RpcError> {
328        use rand::Rng;
329        let mut rng = rand::thread_rng();
330        if self.test_accounts.v1_state_trees.is_empty() {
331            return Err(RpcError::NoStateTreesAvailable);
332        }
333        Ok(self.test_accounts.v1_state_trees
334            [rng.gen_range(0..self.test_accounts.v1_state_trees.len())]
335        .into())
336    }
337
338    fn get_address_tree_v1(&self) -> TreeInfo {
339        TreeInfo {
340            tree: pubkey!("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2"),
341            queue: pubkey!("aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F"),
342            cpi_context: None,
343            next_tree_info: None,
344            tree_type: TreeType::AddressV1,
345        }
346    }
347
348    fn get_address_tree_v2(&self) -> TreeInfo {
349        TreeInfo {
350            tree: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
351            queue: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
352            cpi_context: None,
353            next_tree_info: None,
354            tree_type: TreeType::AddressV2,
355        }
356    }
357
358    async fn create_and_send_versioned_transaction<'a>(
359        &'a mut self,
360        _instructions: &'a [Instruction],
361        _payer: &'a Pubkey,
362        _signers: &'a [&'a Keypair],
363        _address_lookup_tables: &'a [AddressLookupTableAccount],
364    ) -> Result<Signature, RpcError> {
365        unimplemented!(
366            "create_and_send_versioned_transaction is unimplemented for LightProgramTest"
367        );
368    }
369}
370
371impl LightProgramTest {
372    fn maybe_print_logs(&self, logs: impl std::fmt::Display) {
373        // Use enhanced logging if enabled and RUST_BACKTRACE is set
374        if crate::logging::should_use_enhanced_logging(&self.config) {
375            // Enhanced logging will be handled in the transaction processing methods
376            return;
377        }
378
379        // Fallback to basic logging
380        if !self.config.no_logs && cfg!(debug_assertions) && std::env::var("RUST_BACKTRACE").is_ok()
381        {
382            println!("{}", logs);
383        }
384    }
385
386    async fn _send_transaction_with_batched_event(
387        &mut self,
388        transaction: Transaction,
389    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
390        let mut vec = Vec::new();
391
392        let signature = transaction.signatures[0];
393        let transaction_for_logging = transaction.clone(); // Clone for logging
394
395        // Capture lightweight pre-transaction account states for logging
396        let pre_states = crate::logging::capture_account_states(&self.context, &transaction);
397        // Clone context for test assertions (get_pre_transaction_account needs full account data)
398        let pre_context_snapshot = self.context.clone();
399
400        // Simulate the transaction. Currently, in banks-client/server, only
401        // simulations are able to track CPIs. Therefore, simulating is the
402        // only way to retrieve the event.
403        let simulation_result = self.context.simulate_transaction(transaction.clone());
404
405        // Transaction was successful, execute it.
406        self.transaction_counter += 1;
407        let transaction_result = self.context.send_transaction(transaction.clone());
408        let slot = self.context.get_sysvar::<Clock>().slot;
409
410        // Capture post-transaction account states for logging
411        let post_states =
412            crate::logging::capture_account_states(&self.context, &transaction_for_logging);
413
414        // Always try enhanced logging for file output (both success and failure)
415        if crate::logging::should_use_enhanced_logging(&self.config) {
416            crate::logging::log_transaction_enhanced(
417                &self.config,
418                &transaction_for_logging,
419                &transaction_result,
420                &signature,
421                slot,
422                self.transaction_counter,
423                Some(&pre_states),
424                Some(&post_states),
425            );
426        }
427
428        // Handle transaction result after logging
429        let _res = transaction_result.as_ref().map_err(|x| {
430            // Prevent duplicate prints for failing tx.
431            if self.config.log_failed_tx {
432                crate::logging::log_transaction_enhanced_with_console(
433                    &self.config,
434                    &transaction_for_logging,
435                    &transaction_result,
436                    &signature,
437                    slot,
438                    self.transaction_counter,
439                    true, // Enable console output
440                    Some(&pre_states),
441                    Some(&post_states),
442                );
443            }
444            RpcError::TransactionError(x.err.clone())
445        })?;
446
447        // Console logging - if RUST_BACKTRACE is set, print to console too
448        if !self.config.no_logs && std::env::var("RUST_BACKTRACE").is_ok() {
449            if crate::logging::should_use_enhanced_logging(&self.config) {
450                // Print enhanced logs to console
451                crate::logging::log_transaction_enhanced_with_console(
452                    &self.config,
453                    &transaction_for_logging,
454                    &transaction_result,
455                    &signature,
456                    slot,
457                    self.transaction_counter,
458                    true, // Enable console output
459                    Some(&pre_states),
460                    Some(&post_states),
461                );
462
463                // if self.config.log_light_protocol_events {
464                //     if let Some(ref event_data) = event {
465                //         println!("event:\n {:?}", event_data);
466                //     }
467                // }
468            } else {
469                // Fallback to basic log printing
470                self.maybe_print_logs(_res.pretty_logs());
471            }
472        }
473
474        let simulation_result = simulation_result.unwrap();
475        // Try old event deserialization.
476        let event = simulation_result
477            .meta
478            .inner_instructions
479            .iter()
480            .flatten()
481            .find_map(|inner_instruction| {
482                PublicTransactionEvent::try_from_slice(&inner_instruction.instruction.data).ok()
483            });
484        let event = if let Some(event) = event {
485            Some(vec![BatchPublicTransactionEvent {
486                event,
487                ..Default::default()
488            }])
489        } else {
490            // If PublicTransactionEvent wasn't successful deserialize new event.
491            let mut vec_accounts = Vec::<Vec<Pubkey>>::new();
492            let mut program_ids = Vec::new();
493
494            transaction.message.instructions.iter().for_each(|i| {
495                program_ids.push(transaction.message.account_keys[i.program_id_index as usize]);
496                vec.push(i.data.clone());
497                vec_accounts.push(
498                    i.accounts
499                        .iter()
500                        .map(|x| transaction.message.account_keys[*x as usize])
501                        .collect(),
502                );
503            });
504            simulation_result
505                .meta
506                .inner_instructions
507                .iter()
508                .flatten()
509                .find_map(|inner_instruction| {
510                    vec.push(inner_instruction.instruction.data.clone());
511                    program_ids.push(
512                        transaction.message.account_keys
513                            [inner_instruction.instruction.program_id_index as usize],
514                    );
515                    vec_accounts.push(
516                        inner_instruction
517                            .instruction
518                            .accounts
519                            .iter()
520                            .map(|x| transaction.message.account_keys[*x as usize])
521                            .collect(),
522                    );
523                    None::<PublicTransactionEvent>
524                });
525
526            event_from_light_transaction(
527                &program_ids.iter().map(|x| (*x).into()).collect::<Vec<_>>(),
528                vec.as_slice(),
529                vec_accounts
530                    .iter()
531                    .map(|inner_vec| inner_vec.iter().map(|x| (*x).into()).collect())
532                    .collect(),
533            )
534            .or(Ok::<
535                Option<Vec<BatchPublicTransactionEvent>>,
536                ParseIndexerEventError,
537            >(None))?
538        };
539        if self.config.log_light_protocol_events {
540            println!("event:\n {:?}", event);
541        }
542        let event = event.map(|e| (e, signature, slot));
543
544        if let Some(indexer) = self.indexer.as_mut() {
545            if let Some(events) = event.as_ref() {
546                for event in events.0.iter() {
547                    <TestIndexer as TestIndexerExtensions>::add_compressed_accounts_with_token_data(
548                        indexer,
549                        slot,
550                        &event.event,
551                    );
552                }
553            }
554        }
555
556        // Update pre_context only after successful transaction execution
557        self.pre_context = Some(pre_context_snapshot);
558
559        Ok(event)
560    }
561
562    async fn _create_and_send_transaction_with_event<T>(
563        &mut self,
564        instruction: &[Instruction],
565        payer: &Pubkey,
566        signers: &[&Keypair],
567    ) -> Result<Option<(T, Signature, Slot)>, RpcError>
568    where
569        T: BorshDeserialize + Send + Debug,
570    {
571        let transaction = Transaction::new_signed_with_payer(
572            instruction,
573            Some(payer),
574            signers,
575            self.context.latest_blockhash(),
576        );
577
578        let signature = transaction.signatures[0];
579
580        // Cache the current context before transaction execution
581        let pre_context_snapshot = self.context.clone();
582
583        // Simulate the transaction. Currently, in banks-client/server, only
584        // simulations are able to track CPIs. Therefore, simulating is the
585        // only way to retrieve the event.
586        let simulation_result = self
587            .context
588            .simulate_transaction(transaction.clone())
589            .map_err(|x| RpcError::from(x.err))?;
590
591        let event = simulation_result
592            .meta
593            .inner_instructions
594            .iter()
595            .flatten()
596            .find_map(|inner_instruction| {
597                T::try_from_slice(&inner_instruction.instruction.data).ok()
598            });
599        // If transaction was successful, execute it.
600        self.transaction_counter += 1;
601        let _res = self.context.send_transaction(transaction).map_err(|x| {
602            if self.config.log_failed_tx {
603                println!("{}", x.meta.pretty_logs());
604            }
605            RpcError::TransactionError(x.err)
606        })?;
607        self.maybe_print_logs(_res.pretty_logs());
608
609        // Update pre_context only after successful transaction execution
610        self.pre_context = Some(pre_context_snapshot);
611
612        let slot = self.get_slot().await?;
613        let result = event.map(|event| (event, signature, slot));
614        Ok(result)
615    }
616
617    async fn _create_and_send_transaction_with_batched_event(
618        &mut self,
619        instruction: &[Instruction],
620        payer: &Pubkey,
621        signers: &[&Keypair],
622    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
623        let transaction = Transaction::new_signed_with_payer(
624            instruction,
625            Some(payer),
626            signers,
627            self.context.latest_blockhash(),
628        );
629
630        self._send_transaction_with_batched_event(transaction).await
631    }
632}