Skip to content

Commit c723077

Browse files
thlorenztaco-pacocoderabbitai[bot]
authored
fix: prevent removal of delegated/undelegating accounts on eviction (#724)
Co-authored-by: taco-paco <[email protected]> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 17c601b commit c723077

File tree

14 files changed

+308
-11
lines changed

14 files changed

+308
-11
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ target/
1919
# Ledger
2020
test-ledger/
2121
test-ledger-magicblock/
22+
magicblock-test-storage/
2223

2324
# Mac
2425
**/.DS_Store

magicblock-account-cloner/src/bpf_loader_v1.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ impl BpfUpgradableProgramModifications {
5151
rent_epoch: Some(u64::MAX),
5252
delegated: Some(false),
5353
confined: Some(false),
54+
remote_slot: Some(loaded_program.remote_slot),
5455
}
5556
};
5657

@@ -71,6 +72,7 @@ impl BpfUpgradableProgramModifications {
7172
rent_epoch: Some(u64::MAX),
7273
delegated: Some(false),
7374
confined: Some(false),
75+
remote_slot: Some(loaded_program.remote_slot),
7476
}
7577
};
7678

magicblock-account-cloner/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ impl ChainlinkCloner {
100100
executable: Some(request.account.executable()),
101101
delegated: Some(request.account.delegated()),
102102
confined: Some(request.account.confined()),
103+
remote_slot: Some(request.account.remote_slot()),
103104
};
104105

105106
let modify_ix = InstructionUtils::modify_accounts_instruction(vec![
@@ -222,6 +223,7 @@ impl ChainlinkCloner {
222223
// and then deploy it and finally set the authority to match the
223224
// one on chain
224225
let slot = self.accounts_db.slot();
226+
let program_remote_slot = program.remote_slot;
225227
let DeployableV4Program {
226228
pre_deploy_loader_state,
227229
deploy_instruction,
@@ -248,6 +250,7 @@ impl ChainlinkCloner {
248250
executable: Some(true),
249251
data: Some(pre_deploy_loader_state),
250252
confined: Some(false),
253+
remote_slot: Some(program_remote_slot),
251254
..Default::default()
252255
}];
253256
InstructionUtils::modify_accounts_instruction(
@@ -260,6 +263,7 @@ impl ChainlinkCloner {
260263
pubkey: program_id,
261264
data: Some(post_deploy_loader_state),
262265
confined: Some(false),
266+
remote_slot: Some(program_remote_slot),
263267
..Default::default()
264268
}];
265269
InstructionUtils::modify_accounts_instruction(

magicblock-chainlink/src/chainlink/fetch_cloner.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,20 @@ where
217217
if in_bank.undelegating() {
218218
// We expect the account to still be delegated, but with the delegation
219219
// program owner
220-
debug!("Received update for undelegating account {pubkey} delegated in bank={} delegated on chain={}", in_bank.delegated(), account.delegated());
220+
debug!("Received update for undelegating account {pubkey} \
221+
in_bank.delegated={}, \
222+
in_bank.owner={}, \
223+
in_bank.remote_slot={}, \
224+
chain.delegated={}, \
225+
chain.owner={}, \
226+
chain.remote_slot={}",
227+
in_bank.delegated(),
228+
in_bank.owner(),
229+
in_bank.remote_slot(),
230+
account.delegated(),
231+
account.owner(),
232+
account.remote_slot()
233+
);
221234

222235
// This will only be true in the following case:
223236
// 1. a commit was triggered for the account
@@ -414,7 +427,7 @@ where
414427
if log::log_enabled!(log::Level::Trace) {
415428
trace!("Delegation record found for {pubkey}: {delegation_record:?}");
416429
trace!(
417-
"Cloning delegated account: {pubkey} (remote slot {}, owner: {})",
430+
"Resolving delegated account: {pubkey} (remote slot {}, owner: {})",
418431
account.remote_slot(),
419432
delegation_record.owner
420433
);
@@ -1262,7 +1275,16 @@ where
12621275
}
12631276
RefreshDecision::No => {
12641277
// Account is in bank and subscribed correctly - no fetch needed
1265-
trace!("Account {pubkey} found in bank in valid state, no fetch needed");
1278+
if log::log_enabled!(log::Level::Trace) {
1279+
let undelegating = account_in_bank.undelegating();
1280+
let delegated = account_in_bank.delegated();
1281+
let owner = account_in_bank.owner().to_string();
1282+
trace!("Account {pubkey} found in bank in valid state, no fetch needed \
1283+
undelegating = {undelegating}, \
1284+
delegated = {delegated}, \
1285+
owner={owner}"
1286+
);
1287+
}
12661288
in_bank.push(*pubkey);
12671289
}
12681290
}

magicblock-chainlink/src/chainlink/mod.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,37 @@ Kept: {} delegated, {} blacklisted",
219219

220220
task::spawn(async move {
221221
while let Some(pubkey) = removed_accounts_rx.recv().await {
222-
accounts_bank.remove_account(&pubkey);
222+
accounts_bank.remove_account_conditionally(
223+
&pubkey,
224+
|account| {
225+
// Accounts that are still undelegating need to be kept in the bank
226+
// until the undelegation completes on chain.
227+
// Otherwise we might loose data in case the undelegation fails to
228+
// complete.
229+
// Another issue we avoid this way is that an account update received
230+
// before the account completes undelegation would overwrite the in-bank
231+
// account and thus also set unedelegating to false.
232+
let undelegating = account.undelegating();
233+
let delegated = account.delegated();
234+
let remove = !undelegating && !delegated;
235+
if log::log_enabled!(log::Level::Trace) {
236+
if remove {
237+
trace!(
238+
"Removing unsubscribed account '{pubkey}' from bank"
239+
);
240+
} else {
241+
let owner = account.owner().to_string();
242+
trace!(
243+
"Keeping unsubscribed account {pubkey} in bank \
244+
undelegating = {undelegating}, \
245+
delegated = {delegated}, \
246+
owner={owner}"
247+
);
248+
}
249+
}
250+
remove
251+
},
252+
);
223253
}
224254
warn!("Removed accounts channel closed, stopping subscription");
225255
})

magicblock-chainlink/tests/07_redeleg_us_same_slot.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ async fn setup(slot: Slot) -> TestContext {
2323
TestContext::init(slot).await
2424
}
2525

26+
// NOTE: disabled for now since we detect redelegation as follows:
27+
// - if `account.remote_slot >= undelegation_slot` assume it is the first delegation, i.e. it was
28+
// not re-delegated
29+
// - in order to support same slot re-delegation we need an delegation index or similar to
30+
// distinguish the two delegations
2631
#[tokio::test]
32+
#[ignore = "Same slot redelegation is currently not possible and currently it is detected as a single delegation operation."]
2733
async fn test_undelegate_redelegate_to_us_in_same_slot() {
2834
let mut slot: u64 = 11;
2935

magicblock-core/src/traits.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,16 @@ pub trait AccountsBank: Send + Sync + 'static {
1515
&self,
1616
predicate: impl Fn(&Pubkey, &AccountSharedData) -> bool,
1717
) -> usize;
18+
19+
fn remove_account_conditionally(
20+
&self,
21+
pubkey: &Pubkey,
22+
predicate: impl Fn(&AccountSharedData) -> bool,
23+
) {
24+
if let Some(acc) = self.get_account(pubkey) {
25+
if predicate(&acc) {
26+
self.remove_account(pubkey);
27+
}
28+
}
29+
}
1830
}

magicblock-magic-program-api/src/instruction.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ pub struct AccountModification {
120120
pub rent_epoch: Option<u64>,
121121
pub delegated: Option<bool>,
122122
pub confined: Option<bool>,
123+
pub remote_slot: Option<u64>,
123124
}
124125

125126
#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
@@ -133,4 +134,5 @@ pub struct AccountModificationForInstruction {
133134
pub rent_epoch: Option<u64>,
134135
pub delegated: Option<bool>,
135136
pub confined: Option<bool>,
137+
pub remote_slot: Option<u64>,
136138
}

programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,14 @@ pub(crate) fn process_mutate_accounts(
223223
);
224224
account.borrow_mut().set_confined(confined);
225225
}
226+
if let Some(remote_slot) = modification.remote_slot {
227+
ic_msg!(
228+
invoke_context,
229+
"MutateAccounts: setting remote_slot to {}",
230+
remote_slot
231+
);
232+
account.borrow_mut().set_remote_slot(remote_slot);
233+
}
226234
}
227235

228236
if lamports_to_debit != 0 {
@@ -329,6 +337,7 @@ mod tests {
329337
rent_epoch: None,
330338
delegated: Some(true),
331339
confined: Some(true),
340+
remote_slot: None,
332341
};
333342
let ix = InstructionUtils::modify_accounts_instruction(vec![
334343
modification.clone(),
@@ -623,4 +632,46 @@ mod tests {
623632
}
624633
);
625634
}
635+
636+
#[test]
637+
fn test_mod_remote_slot() {
638+
init_logger!();
639+
640+
let mod_key = Pubkey::new_unique();
641+
let remote_slot = 12345u64;
642+
let mut account_data = {
643+
let mut map = HashMap::new();
644+
map.insert(mod_key, AccountSharedData::new(100, 0, &mod_key));
645+
map
646+
};
647+
ensure_started_validator(&mut account_data);
648+
649+
let ix = InstructionUtils::modify_accounts_instruction(vec![
650+
AccountModification {
651+
pubkey: mod_key,
652+
remote_slot: Some(remote_slot),
653+
..Default::default()
654+
},
655+
]);
656+
let transaction_accounts = ix
657+
.accounts
658+
.iter()
659+
.flat_map(|acc| {
660+
account_data
661+
.remove(&acc.pubkey)
662+
.map(|shared_data| (acc.pubkey, shared_data))
663+
})
664+
.collect();
665+
666+
let mut accounts = process_instruction(
667+
ix.data.as_slice(),
668+
transaction_accounts,
669+
ix.accounts,
670+
Ok(()),
671+
);
672+
673+
let _account_authority = accounts.drain(0..1).next().unwrap();
674+
let modified_account = accounts.drain(0..1).next().unwrap();
675+
assert_eq!(modified_account.remote_slot(), remote_slot);
676+
}
626677
}

programs/magicblock/src/utils/instruction_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ impl InstructionUtils {
182182
rent_epoch: account_modification.rent_epoch,
183183
delegated: account_modification.delegated,
184184
confined: account_modification.confined,
185+
remote_slot: account_modification.remote_slot,
185186
};
186187
account_mods.insert(
187188
account_modification.pubkey,

0 commit comments

Comments
 (0)