Skip to content

Commit e2663c4

Browse files
cleanup
1 parent 23a7f2b commit e2663c4

6 files changed

Lines changed: 87 additions & 39 deletions

File tree

forester/src/compressible/bootstrap_helpers.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -393,16 +393,17 @@ where
393393
{
394394
info!("Starting bootstrap of {} accounts", label);
395395

396-
// Set up shutdown flag
396+
// Set up shutdown flag and listener task
397397
let shutdown_flag = Arc::new(AtomicBool::new(false));
398398

399-
if let Some(rx) = shutdown_rx {
399+
// Spawn shutdown listener and keep handle for cleanup
400+
let shutdown_listener_handle = shutdown_rx.map(|rx| {
400401
let shutdown_flag_clone = shutdown_flag.clone();
401402
tokio::spawn(async move {
402403
let _ = rx.await;
403404
shutdown_flag_clone.store(true, Ordering::SeqCst);
404-
});
405-
}
405+
})
406+
});
406407

407408
let client = reqwest::Client::new();
408409

@@ -413,15 +414,22 @@ where
413414

414415
let result = if is_localhost(rpc_url) {
415416
debug!("Detected localhost, using standard getProgramAccounts");
416-
let (fetched, inserted) = bootstrap_standard_api(
417+
let api_result = bootstrap_standard_api(
417418
&client,
418419
rpc_url,
419420
program_id,
420421
filters,
421422
Some(&shutdown_flag),
422423
process_fn,
423424
)
424-
.await?;
425+
.await;
426+
427+
// Abort shutdown listener before returning (success or error)
428+
if let Some(handle) = shutdown_listener_handle {
429+
handle.abort();
430+
}
431+
432+
let (fetched, inserted) = api_result?;
425433

426434
info!(
427435
"{} bootstrap complete: {} fetched, {} inserted",
@@ -435,15 +443,22 @@ where
435443
}
436444
} else {
437445
debug!("Using getProgramAccountsV2 with pagination");
438-
let (pages, fetched, inserted) = bootstrap_v2_api(
446+
let api_result = bootstrap_v2_api(
439447
&client,
440448
rpc_url,
441449
program_id,
442450
filters,
443451
Some(&shutdown_flag),
444452
process_fn,
445453
)
446-
.await?;
454+
.await;
455+
456+
// Abort shutdown listener before returning (success or error)
457+
if let Some(handle) = shutdown_listener_handle {
458+
handle.abort();
459+
}
460+
461+
let (pages, fetched, inserted) = api_result?;
447462

448463
info!(
449464
"{} bootstrap complete: {} pages, {} fetched, {} inserted",

forester/src/compressible/mint/compressor.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,14 @@ impl<R: Rpc + Indexer> MintCompressor<R> {
159159
"Batched CompressAndCloseMint tx (chunk {}) confirmed: {}",
160160
chunk_idx, signature
161161
);
162+
// Only return confirmed signatures to callers
163+
signatures.push(signature);
162164
} else {
163165
tracing::warn!(
164166
"CompressAndCloseMint tx not confirmed: {} - accounts kept in tracker for retry",
165167
signature
166168
);
167169
}
168-
169-
signatures.push(signature);
170170
}
171171

172172
Ok(signatures)

forester/src/compressible/pda/compressor.rs

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::sync::{
33
Arc,
44
};
55

6-
use borsh::BorshDeserialize;
76
use forester_utils::rpc_pool::SolanaRpcPool;
87
use futures::StreamExt;
98
use light_client::{
@@ -81,7 +80,7 @@ impl<R: Rpc + Indexer> PdaCompressor<R> {
8180
// Get the compressible config PDA for this program (config_bump = 0)
8281
let (config_pda, _) = LightConfig::derive_pda(program_id, 0);
8382

84-
// Fetch the config to get rent_sponsor and address_space
83+
// Fetch the config account
8584
let rpc = self.rpc_pool.get_connection().await?;
8685
let config_account = rpc
8786
.get_account(config_pda)
@@ -91,38 +90,22 @@ impl<R: Rpc + Indexer> PdaCompressor<R> {
9190
anyhow::anyhow!("Config account not found for program {}", program_id)
9291
})?;
9392

94-
// Verify account owner matches program_id (mirrors LightConfig::load_checked)
95-
if config_account.owner != *program_id {
96-
return Err(anyhow::anyhow!(
97-
"Config account owner mismatch. Expected: {}. Found: {}",
98-
program_id,
99-
config_account.owner
100-
));
101-
}
102-
103-
// Deserialize config
104-
let config = LightConfig::try_from_slice(&config_account.data)
105-
.map_err(|e| anyhow::anyhow!("Failed to deserialize config: {:?}", e))?;
106-
107-
// Validate config (checks config_bump == 0 and other constraints)
108-
config.validate().map_err(|e| {
93+
// Load and validate config using SDK validator
94+
// This checks: owner == program_id, config_bump == 0, PDA derivation, and other constraints
95+
let config = LightConfig::load_checked_client(
96+
&config_account.data,
97+
&config_account.owner,
98+
&config_pda,
99+
program_id,
100+
)
101+
.map_err(|e| {
109102
anyhow::anyhow!(
110-
"LightConfig validation failed for program {}: {:?}",
103+
"LightConfig validation failed for program {}: {}",
111104
program_id,
112105
e
113106
)
114107
})?;
115108

116-
// Verify PDA derivation matches (mirrors LightConfig::load_checked)
117-
let (expected_pda, _) = LightConfig::derive_pda(program_id, config.config_bump);
118-
if expected_pda != config_pda {
119-
return Err(anyhow::anyhow!(
120-
"Config PDA derivation mismatch. Expected: {}. Found: {}",
121-
expected_pda,
122-
config_pda
123-
));
124-
}
125-
126109
let rent_sponsor = config.rent_sponsor;
127110
let compression_authority = config.compression_authority;
128111
let address_tree = *config

forester/tests/test_compressible_ctoken.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ async fn run_bootstrap_test(
529529
let account_state = accounts
530530
.iter()
531531
.find(|acc| acc.pubkey == *pubkey)
532-
.expect(&format!("Bootstrap should have found account {}", pubkey));
532+
.unwrap_or_else(|| panic!("Bootstrap should have found account {}", pubkey));
533533

534534
println!(
535535
"Verifying account {}: mint={:?}, lamports={}",

program-libs/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Some crates depend on external Light Protocol crates not in program-libs:
6363
## Testing
6464

6565
Unit tests run with `cargo test`:
66+
6667
```bash
6768
cargo test -p light-hasher --all-features
6869
cargo test -p light-compressed-account --all-features

sdk-libs/sdk/src/interface/config.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,55 @@ impl LightConfig {
145145

146146
Ok(config)
147147
}
148+
149+
/// Client-side version of load_checked for off-chain validation.
150+
///
151+
/// Validates config account data fetched via RPC, checking owner and PDA derivation.
152+
/// This mirrors the on-chain `load_checked` but works with client-side Account data.
153+
///
154+
/// # Arguments
155+
/// * `account_data` - The account data bytes from RPC
156+
/// * `account_owner` - The owner pubkey of the fetched account
157+
/// * `account_key` - The pubkey of the fetched account (config_pda)
158+
/// * `program_id` - The program that should own the config
159+
///
160+
/// # Returns
161+
/// * `Ok(LightConfig)` if validation passes
162+
/// * `Err(String)` with description if validation fails
163+
pub fn load_checked_client(
164+
account_data: &[u8],
165+
account_owner: &Pubkey,
166+
account_key: &Pubkey,
167+
program_id: &Pubkey,
168+
) -> Result<Self, String> {
169+
// CHECK: Owner matches program_id
170+
if account_owner != program_id {
171+
return Err(format!(
172+
"Config account owner mismatch. Expected: {}. Found: {}",
173+
program_id, account_owner
174+
));
175+
}
176+
177+
// Deserialize config
178+
let config = Self::try_from_slice(account_data)
179+
.map_err(|err| format!("Failed to deserialize config data: {:?}", err))?;
180+
181+
// Validate config (checks version, address_space, config_bump)
182+
config
183+
.validate()
184+
.map_err(|err| format!("Config validation failed: {:?}", err))?;
185+
186+
// CHECK: PDA derivation matches account key
187+
let (expected_pda, _) = Self::derive_pda(program_id, config.config_bump);
188+
if expected_pda != *account_key {
189+
return Err(format!(
190+
"Config PDA derivation mismatch. Expected: {}. Found: {}",
191+
expected_pda, account_key
192+
));
193+
}
194+
195+
Ok(config)
196+
}
148197
}
149198

150199
/// Creates a new compressible config PDA

0 commit comments

Comments
 (0)