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