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