Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/packages/npm-package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"name": "@magicblock-labs/ephemeral-validator",
"version": "0.3.1",
"description": "MagicBlock Ephemeral Validator",
"homepage": "https://github.com/magicblock-labs/ephemeral-validator#readme",
"homepage": "https://github.com/magicblock-labs/magicblock-validator#readme",
"bugs": {
"url": "https://github.com/magicblock-labs/ephemeral-validator/issues"
"url": "https://github.com/magicblock-labs/magicblock-validator/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/magicblock-labs/ephemeral-validator.git"
"url": "https://github.com/magicblock-labs/magicblock-validator.git"
},
"license": "Business Source License 1.1",
"bin": {
Expand Down
4 changes: 2 additions & 2 deletions .github/packages/npm-package/package.json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"version": "0.3.1",
"repository": {
"type": "git",
"url": "git+https://github.com/magicblock-labs/ephemeral-validator.git"
"url": "git+https://github.com/magicblock-labs/magicblock-validator.git"
},
"bugs": {
"url": "https://github.com/magicblock-labs/ephemeral-validator/issues"
"url": "https://github.com/magicblock-labs/magicblock-validator/issues"
},
"license": "Business Source License 1.1",
"private": false,
Expand Down
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ toml = "0.8.13"
tonic-build = "0.9.2"
url = "2.5.0"

# SPL Token crates used across the workspace
spl-token = "7.0"
spl-token-2022 = "7.0"

[workspace.dependencies.solana-svm]
git = "https://github.com/magicblock-labs/magicblock-svm.git"
rev = "3e9456ec4"
Expand Down
3 changes: 3 additions & 0 deletions magicblock-chainlink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ solana-account-decoder = { workspace = true }
solana-account-decoder-client-types = { workspace = true }
solana-loader-v3-interface = { workspace = true, features = ["serde"] }
solana-loader-v4-interface = { workspace = true, features = ["serde"] }
solana-program = { workspace = true }
solana-pubkey = { workspace = true }
solana-pubsub-client = { workspace = true }
solana-rpc-client = { workspace = true }
Expand All @@ -30,6 +31,8 @@ solana-sdk-ids = { workspace = true }
solana-signer = { workspace = true }
solana-system-interface = { workspace = true }
solana-transaction-error = { workspace = true }
spl-token = { workspace = true }
spl-token-2022 = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tokio-stream = { workspace = true }
Expand Down
126 changes: 115 additions & 11 deletions magicblock-chainlink/src/chainlink/fetch_cloner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ use dlp::{
pda::delegation_record_pda_from_delegated_account, state::DelegationRecord,
};
use log::*;
// Shared ATA/eATA helpers
use magicblock_core::token_programs::{
is_ata, try_derive_eata_address_and_bump, MaybeIntoAta,
};
use magicblock_core::traits::AccountsBank;
use magicblock_metrics::metrics::{self, AccountFetchOrigin};
use solana_account::{AccountSharedData, ReadableAccount};
Expand Down Expand Up @@ -664,14 +668,15 @@ where

trace!("Fetched {accs:?}");

let (not_found, plain, owned_by_deleg, programs) =
let (not_found, plain, owned_by_deleg, programs, atas) =
accs.into_iter().zip(pubkeys).fold(
(vec![], vec![], vec![], vec![]),
(vec![], vec![], vec![], vec![], vec![]),
|(
mut not_found,
mut plain,
mut owned_by_deleg,
mut programs,
mut atas,
),
(acc, &pubkey)| {
use RemoteAccount::*;
Expand All @@ -698,8 +703,7 @@ where
// to fail
if !account_shared_data
.owner()
.eq(&solana_sdk::native_loader::id(
))
.eq(&solana_sdk::native_loader::id())
{
programs.push((
pubkey,
Expand All @@ -711,6 +715,13 @@ where
"Not cloning native loader program account: {pubkey} (should have been blacklisted)",
);
}
} else if let Some(ata) = is_ata(&pubkey, &account_shared_data) {
atas.push((
pubkey,
account_shared_data,
ata,
slot,
));
} else {
plain.push(AccountCloneRequest {
pubkey,
Expand All @@ -725,7 +736,7 @@ where
};
}
}
(not_found, plain, owned_by_deleg, programs)
(not_found, plain, owned_by_deleg, programs, atas)
},
);

Expand All @@ -746,8 +757,12 @@ where
.iter()
.map(|(p, _, _)| p.to_string())
.collect::<Vec<_>>();
let atas = atas
.iter()
.map(|(a, _, _, _)| a.to_string())
.collect::<Vec<_>>();
trace!(
"Fetched accounts: \nnot_found: {not_found:?} \nplain: {plain:?} \nowned_by_deleg: {owned_by_deleg:?}\nprograms: {programs:?}",
"Fetched accounts: \nnot_found: {not_found:?} \nplain: {plain:?} \nowned_by_deleg: {owned_by_deleg:?}\nprograms: {programs:?} \natas: {atas:?}",
);
}

Expand Down Expand Up @@ -808,7 +823,7 @@ where
let mut missing_delegation_record = vec![];

// We remove all new subs for accounts that were not found or already in the bank
let (accounts_to_clone, record_subs) = {
let (mut accounts_to_clone, record_subs) = {
let joined = fetch_with_delegation_record_join_set.join_all().await;
let (errors, accounts_fully_resolved) = joined.into_iter().fold(
(vec![], vec![]),
Expand Down Expand Up @@ -871,7 +886,7 @@ where
// NOTE: failing here is fine when resolving all accounts for a transaction
// since if something is off we better not run it anyways
// However we may consider a different behavior when user is getting
// mutliple accounts.
// multiple accounts.
let delegation_record = match Self::parse_delegation_record(
delegation_record_data.data(),
delegation_record_pubkey,
Expand Down Expand Up @@ -1091,14 +1106,102 @@ where
));
}

// Cancel new subs for accounts we don't clone
// We will compute subscription cancellations after ATA handling, once accounts_to_clone is finalized

// Handle ATAs: for each detected ATA, we derive the eATA PDA, subscribe to both,
// and, if the ATA is delegated to us and the eATA exists, we clone the eATA data
// into the ATA in the bank.
if !atas.is_empty() {
let mut ata_join_set = JoinSet::new();

// Subscribe first so subsequent fetches are kept up-to-date
for (ata_pubkey, _, ata_info, slot_for_ata) in &atas {
let _ = self.subscribe_to_account(ata_pubkey).await;
if let Some((eata, _)) = try_derive_eata_address_and_bump(
&ata_info.owner,
&ata_info.mint,
) {
let _ = self.subscribe_to_account(&eata).await;

let effective_slot =
if let Some(min_slot) = min_context_slot {
min_slot.max(*slot_for_ata)
} else {
*slot_for_ata
};
ata_join_set.spawn(self.task_to_fetch_with_companion(
*ata_pubkey,
eata,
effective_slot,
fetch_origin,
));
}
}

let ata_results = ata_join_set.join_all().await;
for res in ata_results.into_iter() {
match res {
Ok(Ok(AccountWithCompanion {
pubkey: ata_pubkey,
account: ata_account,
companion_pubkey: eata_pubkey,
companion_account: maybe_eata_account,
})) => {
// Convert to AccountSharedData using the bank snapshot
let mut account_to_clone =
ata_account.account_shared_data_cloned();
let mut commit_frequency_ms = None;
if let Some(eata_acc) = maybe_eata_account {
let eata_shared =
eata_acc.account_shared_data_cloned();
if let Some(deleg) = self
.fetch_and_parse_delegation_record(
eata_pubkey,
self.remote_account_provider.chain_slot(),
fetch_origin,
)
.await
{
let is_delegated_to_us = deleg
.authority
.eq(&self.validator_pubkey)
|| deleg.authority.eq(&Pubkey::default());
if is_delegated_to_us {
if let Some(projected_ata) =
eata_shared.maybe_into_ata(deleg.owner)
{
account_to_clone = projected_ata;
account_to_clone.set_delegated(true);
commit_frequency_ms =
Some(deleg.commit_frequency_ms);
}
}
}
}

accounts_to_clone.push(AccountCloneRequest {
pubkey: ata_pubkey,
account: account_to_clone,
commit_frequency_ms,
});
}
Ok(Err(err)) => {
warn!("Failed to resolve ATA/eATA companion: {err}");
}
Err(join_err) => {
warn!("Failed to join ATA/eATA fetch task: {join_err}");
}
}
}
}

// Compute sub cancellations now since we may potentially fail during a cloning step
let acc_subs = pubkeys.iter().filter(|pubkey| {
!accounts_to_clone
.iter()
.any(|request| request.pubkey.eq(pubkey))
&& !loaded_programs.iter().any(|p| p.program_id.eq(pubkey))
});

// Cancel subs for delegated accounts (accounts we clone but don't need to watch)
let delegated_acc_subs: HashSet<Pubkey> = accounts_to_clone
.iter()
Expand All @@ -1111,7 +1214,6 @@ where
})
.collect();

// Handle sub cancelation now since we may potentially fail during a cloning step
cancel_subs(
&self.remote_account_provider,
CancelStrategy::Hybrid {
Expand Down Expand Up @@ -3227,3 +3329,5 @@ mod tests {
);
}
}

// local helpers moved to magicblock_core::token_programs
58 changes: 58 additions & 0 deletions magicblock-chainlink/src/testing/eatas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
pub use magicblock_core::token_programs::{
derive_ata, derive_eata, EphemeralAta, EATA_PROGRAM_ID,
SPL_TOKEN_PROGRAM_ID,
};
use solana_account::Account;
use solana_program::{program_option::COption, program_pack::Pack};
use solana_pubkey::Pubkey;
use solana_sdk::rent::Rent;
use spl_token::state::{Account as SplAccount, AccountState};

pub fn create_ata_account(owner: &Pubkey, mint: &Pubkey) -> Account {
let token_account = SplAccount {
mint: *mint,
owner: *owner,
amount: 0,
delegate: COption::None,
state: AccountState::Initialized,
is_native: COption::None,
delegated_amount: 0,
close_authority: COption::None,
};

let mut data = vec![0u8; SplAccount::LEN];
SplAccount::pack(token_account, &mut data).expect("pack spl token account");
let lamports = Rent::default().minimum_balance(data.len());

Account {
owner: SPL_TOKEN_PROGRAM_ID,
data,
lamports,
executable: false,
..Default::default()
}
}

pub fn create_eata_account(
owner: &Pubkey,
mint: &Pubkey,
amount: u64,
delegate: bool,
) -> Account {
let mut data = Vec::with_capacity(64 + 8);
data.extend_from_slice(owner.as_ref());
data.extend_from_slice(mint.as_ref());
data.extend_from_slice(&amount.to_le_bytes());
let lamports = Rent::default().minimum_balance(data.len());

let owner = if delegate { dlp::ID } else { EATA_PROGRAM_ID };

Account {
owner,
data,
lamports,
..Default::default()
}
}
Comment on lines +36 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Variable shadowing reduces readability.

The owner parameter (line 37) is shadowed by the local owner binding (line 48), which can confuse readers about which owner is used in the Account construction.

 pub fn create_eata_account(
     owner: &Pubkey,
     mint: &Pubkey,
     amount: u64,
     delegate: bool,
 ) -> Account {
     let mut data = Vec::with_capacity(64 + 8);
     data.extend_from_slice(owner.as_ref());
     data.extend_from_slice(mint.as_ref());
     data.extend_from_slice(&amount.to_le_bytes());
     let lamports = Rent::default().minimum_balance(data.len());

-    let owner = if delegate { dlp::ID } else { EATA_PROGRAM_ID };
+    let account_owner = if delegate { dlp::ID } else { EATA_PROGRAM_ID };

     Account {
-        owner,
+        owner: account_owner,
         data,
         lamports,
         ..Default::default()
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn create_eata_account(
owner: &Pubkey,
mint: &Pubkey,
amount: u64,
delegate: bool,
) -> Account {
let mut data = Vec::with_capacity(64 + 8);
data.extend_from_slice(owner.as_ref());
data.extend_from_slice(mint.as_ref());
data.extend_from_slice(&amount.to_le_bytes());
let lamports = Rent::default().minimum_balance(data.len());
let owner = if delegate { dlp::ID } else { EATA_PROGRAM_ID };
Account {
owner,
data,
lamports,
..Default::default()
}
}
pub fn create_eata_account(
owner: &Pubkey,
mint: &Pubkey,
amount: u64,
delegate: bool,
) -> Account {
let mut data = Vec::with_capacity(64 + 8);
data.extend_from_slice(owner.as_ref());
data.extend_from_slice(mint.as_ref());
data.extend_from_slice(&amount.to_le_bytes());
let lamports = Rent::default().minimum_balance(data.len());
let account_owner = if delegate { dlp::ID } else { EATA_PROGRAM_ID };
Account {
owner: account_owner,
data,
lamports,
..Default::default()
}
}
🤖 Prompt for AI Agents
In magicblock-chainlink/src/testing/eatas.rs around lines 36 to 56, the function
shadows the parameter owner with a local variable owner when choosing the
account owner for Account; rename the local binding to something non-shadowing
(e.g., account_owner or program_owner), use that new name in the Account {
owner: account_owner, ... } construction, and keep the original owner parameter
untouched to improve readability.


// Reuse EphemeralAta definition from magicblock_core::token_programs
2 changes: 2 additions & 0 deletions magicblock-chainlink/src/testing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub mod cloner_stub;
#[cfg(any(test, feature = "dev-context"))]
pub mod deleg;
#[cfg(any(test, feature = "dev-context"))]
pub mod eatas;
#[cfg(any(test, feature = "dev-context"))]
pub mod rpc_client_mock;
#[cfg(any(test, feature = "dev-context"))]
pub mod utils;
Expand Down
2 changes: 2 additions & 0 deletions magicblock-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ solana-transaction = { workspace = true, features = ["blake3", "verify"] }
solana-transaction-context = { workspace = true }
solana-transaction-error = { workspace = true }
magicblock-magic-program-api = { workspace = true }
spl-token = { workspace = true }
spl-token-2022 = { workspace = true }
1 change: 1 addition & 0 deletions magicblock-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ macro_rules! debug_panic {

pub mod link;
pub mod tls;
pub mod token_programs;
pub mod traits;
Loading