From c90306bc6ab89d3b85ad9260c2ded8f1b74e2ca0 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 17 Nov 2025 09:57:15 +0400 Subject: [PATCH 1/3] feat: re-add auto airdrop --- magicblock-api/src/magic_validator.rs | 1 + .../src/chainlink/fetch_cloner.rs | 27 +++++++++++++ magicblock-chainlink/src/chainlink/mod.rs | 39 ++++++++++++++++++- .../tests/utils/test_context.rs | 1 + .../test-chainlink/src/ixtest_context.rs | 1 + .../test-chainlink/src/test_context.rs | 1 + .../tests/auto_airdrop_feepayer.rs | 1 - 7 files changed, 68 insertions(+), 3 deletions(-) diff --git a/magicblock-api/src/magic_validator.rs b/magicblock-api/src/magic_validator.rs index 749489458..397d5fef9 100644 --- a/magicblock-api/src/magic_validator.rs +++ b/magicblock-api/src/magic_validator.rs @@ -422,6 +422,7 @@ impl MagicValidator { validator_pubkey, faucet_pubkey, chainlink_config, + config.accounts.clone.auto_airdrop_lamports, ) .await?; diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner.rs b/magicblock-chainlink/src/chainlink/fetch_cloner.rs index 614629ea4..f27d59b7e 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner.rs @@ -14,6 +14,7 @@ use log::*; use magicblock_core::traits::AccountsBank; use solana_account::{AccountSharedData, ReadableAccount}; use solana_pubkey::Pubkey; +use solana_sdk::system_program; use tokio::{ sync::{mpsc, oneshot}, task, @@ -1230,6 +1231,32 @@ where ) -> ChainlinkResult> { Ok(self.remote_account_provider.try_get_removed_account_rx()?) } + + /// Best-effort airdrop helper: if the account doesn't exist in the bank or has 0 lamports, + /// create/overwrite it as a plain system account with the provided lamports using the cloner path. + pub async fn airdrop_account_if_empty( + &self, + pubkey: Pubkey, + lamports: u64, + ) -> ClonerResult<()> { + if lamports == 0 { + return Ok(()); + } + if let Some(acc) = self.accounts_bank.get_account(&pubkey) { + if acc.lamports() > 0 { + return Ok(()); + } + } + // Build a plain system account with the requested balance + let account = + AccountSharedData::new(lamports, 0, &system_program::id()); + debug!( + "Auto-airdropping {} lamports to new/empty account {}", + lamports, pubkey + ); + let _sig = self.cloner.clone_account(pubkey, account).await?; + Ok(()) + } } // ----------------- diff --git a/magicblock-chainlink/src/chainlink/mod.rs b/magicblock-chainlink/src/chainlink/mod.rs index 4b46fe768..5c5202670 100644 --- a/magicblock-chainlink/src/chainlink/mod.rs +++ b/magicblock-chainlink/src/chainlink/mod.rs @@ -53,6 +53,9 @@ pub struct Chainlink< validator_id: Pubkey, faucet_id: Pubkey, + + /// If > 0, automatically airdrop this many lamports to feepayers when they are new/empty + auto_airdrop_lamports: u64, } impl @@ -63,6 +66,7 @@ impl fetch_cloner: Option>>, validator_pubkey: Pubkey, faucet_pubkey: Pubkey, + auto_airdrop_lamports: u64, ) -> ChainlinkResult { let removed_accounts_sub = if let Some(fetch_cloner) = &fetch_cloner { let removed_accounts_rx = @@ -80,6 +84,7 @@ impl removed_accounts_sub, validator_id: validator_pubkey, faucet_id: faucet_pubkey, + auto_airdrop_lamports, }) } @@ -91,6 +96,7 @@ impl validator_pubkey: Pubkey, faucet_pubkey: Pubkey, config: ChainlinkConfig, + auto_airdrop_lamports: u64, ) -> ChainlinkResult< Chainlink< ChainRpcClientImpl, @@ -129,6 +135,7 @@ impl fetch_cloner, validator_pubkey, faucet_pubkey, + auto_airdrop_lamports, ) } @@ -259,8 +266,36 @@ Kept: {} delegated, {} blacklisted", } let mark_empty_if_not_found = (!mark_empty_if_not_found.is_empty()) .then(|| &mark_empty_if_not_found[..]); - self.ensure_accounts(&pubkeys, mark_empty_if_not_found) - .await + let res = self + .ensure_accounts(&pubkeys, mark_empty_if_not_found) + .await?; + + // Best-effort auto airdrop for fee payer if configured and still empty locally + if self.auto_airdrop_lamports > 0 { + if let Some(fetch_cloner) = self.fetch_cloner() { + let lamports = self + .accounts_bank + .get_account(feepayer) + .map(|a| a.lamports()) + .unwrap_or(0); + if lamports == 0 { + if let Err(err) = fetch_cloner + .airdrop_account_if_empty( + *feepayer, + self.auto_airdrop_lamports, + ) + .await + { + warn!( + "Auto airdrop for feepayer {} failed: {:?}", + feepayer, err + ); + } + } + } + } + + Ok(res) } /// Same as fetch accounts, but does not return the accounts, just diff --git a/magicblock-chainlink/tests/utils/test_context.rs b/magicblock-chainlink/tests/utils/test_context.rs index 7c9bbad55..3e41702de 100644 --- a/magicblock-chainlink/tests/utils/test_context.rs +++ b/magicblock-chainlink/tests/utils/test_context.rs @@ -105,6 +105,7 @@ impl TestContext { fetch_cloner, validator_pubkey, faucet_pubkey, + 0, ) .unwrap(); Self { diff --git a/test-integration/test-chainlink/src/ixtest_context.rs b/test-integration/test-chainlink/src/ixtest_context.rs index 8053eee75..bb5ca5e51 100644 --- a/test-integration/test-chainlink/src/ixtest_context.rs +++ b/test-integration/test-chainlink/src/ixtest_context.rs @@ -140,6 +140,7 @@ impl IxtestContext { fetch_cloner, validator_kp.pubkey(), faucet_kp.pubkey(), + 0, ) .unwrap(); diff --git a/test-integration/test-chainlink/src/test_context.rs b/test-integration/test-chainlink/src/test_context.rs index f0082fb49..a90d3d986 100644 --- a/test-integration/test-chainlink/src/test_context.rs +++ b/test-integration/test-chainlink/src/test_context.rs @@ -109,6 +109,7 @@ impl TestContext { fetch_cloner, validator_pubkey, faucet_pubkey, + 0, ) .unwrap(); Self { diff --git a/test-integration/test-config/tests/auto_airdrop_feepayer.rs b/test-integration/test-config/tests/auto_airdrop_feepayer.rs index 9bf018840..1bed43950 100644 --- a/test-integration/test-config/tests/auto_airdrop_feepayer.rs +++ b/test-integration/test-config/tests/auto_airdrop_feepayer.rs @@ -11,7 +11,6 @@ use magicblock_config::{ use solana_sdk::{signature::Keypair, signer::Signer, system_instruction}; use test_kit::init_logger; -#[ignore = "Auto airdrop is not generally supported at this point, we will add this back as needed"] #[test] fn test_auto_airdrop_feepayer_balance_after_tx() { init_logger!(); From 11e068c86b52a6197e09aca1a345e6c0e8e97af0 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 17 Nov 2025 10:57:37 +0400 Subject: [PATCH 2/3] chore: lint --- magicblock-chainlink/src/chainlink/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/magicblock-chainlink/src/chainlink/mod.rs b/magicblock-chainlink/src/chainlink/mod.rs index 5c5202670..d184e08f1 100644 --- a/magicblock-chainlink/src/chainlink/mod.rs +++ b/magicblock-chainlink/src/chainlink/mod.rs @@ -88,6 +88,7 @@ impl }) } + #[allow(clippy::too_many_arguments)] pub async fn try_new_from_endpoints( endpoints: &[Endpoint], commitment: CommitmentConfig, From 2af589bc497851535674625858dc970be8989ce7 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Mon, 17 Nov 2025 11:09:40 +0400 Subject: [PATCH 3/3] fix: tests --- magicblock-aperture/src/tests.rs | 1 + magicblock-aperture/tests/setup.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/magicblock-aperture/src/tests.rs b/magicblock-aperture/src/tests.rs index 8d49c818c..643fbb737 100644 --- a/magicblock-aperture/src/tests.rs +++ b/magicblock-aperture/src/tests.rs @@ -42,6 +42,7 @@ fn chainlink(accounts_db: &Arc) -> ChainlinkImpl { None, Pubkey::new_unique(), Pubkey::new_unique(), + 0, ) .expect("Failed to create Chainlink") } diff --git a/magicblock-aperture/tests/setup.rs b/magicblock-aperture/tests/setup.rs index decfacf9d..6160f75e0 100644 --- a/magicblock-aperture/tests/setup.rs +++ b/magicblock-aperture/tests/setup.rs @@ -62,6 +62,7 @@ fn chainlink(accounts_db: &Arc) -> Arc { None, Pubkey::new_unique(), Pubkey::new_unique(), + 0, ) .expect("Failed to create Chainlink"), )