-
Notifications
You must be signed in to change notification settings - Fork 23
feat: detecting accounts stuck in undelegating state and fixing that #664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
8dc65b4
fix: flip undelegating flag in central place
thlorenz 59bb9f8
chore: remove redundant undelegating flag flip
thlorenz 77e22ed
feat: central place to decide if undelegate succeeded + we should ove…
thlorenz 9a8b558
chore: untoggle undelegating when cloning/mutating account
thlorenz 0bc27e7
chore: slight improvements to commit program logs
thlorenz a5f2600
chore: logging querying of undelegating
thlorenz 7721a22
chore: fix should override inputs
thlorenz 161db87
Merge branch 'master' into thlorenz/the-great-unborking
thlorenz e64771a
chore: clearify undelegating detection + add logs
thlorenz e6de339
chore: fix location where we check for undelegating accounts
thlorenz a00d897
chore: proper metric updates related to refetching accounts
thlorenz 2a09fc3
chore: improve comments/logging around undelegating accounts
thlorenz 0e72732
chore: remove refetching of accounts we should be watching
thlorenz e7334e0
fix: ensure we don't keep deleg record subscription
thlorenz ef27ffa
chore: adapt/add unit tests
thlorenz e4104f4
Merge branch 'master' into thlorenz/the-great-unborking
thlorenz 533f3ce
fix: formatting issue
thlorenz 0e4173d
chore: update cargo lock
thlorenz c062b0a
Merge branch 'master' into thlorenz/the-great-unborking
thlorenz 139449f
fix: lint
thlorenz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
211 changes: 211 additions & 0 deletions
211
magicblock-chainlink/src/chainlink/account_still_undelegating_on_chain.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| use dlp::state::DelegationRecord; | ||
| use log::*; | ||
| use solana_pubkey::Pubkey; | ||
|
|
||
| /// Decides if an account that is undelegating should be updated | ||
| /// (overwritten) by the remote account state and the `undelegating` flag cleared. | ||
| /// | ||
| /// The only case when an account should not be updated is when the following is true: | ||
| /// | ||
| /// - account is still delegated to us on chain | ||
| /// - the delegation slot is older than the slot at which we last fetched | ||
| /// the account state from chain. | ||
| /// | ||
| /// # Arguments | ||
| /// - `pubkey`: the account pubkey | ||
| /// - `is_delegated_on_chain`: whether the account is currently delegated to us on chain | ||
| /// - `remote_slot_in_bank`: the chain slot at which we last fetched and cloned state | ||
| /// of the account in our bank | ||
| /// - `delegation_record`: the delegation record associated with the account in our bank, if found | ||
| /// - `validator_auth`: the validator authority pubkey | ||
| /// - returns `true` if the account is still undelegating, `false` otherwise. | ||
| pub(crate) fn account_still_undelegating_on_chain( | ||
| pubkey: &Pubkey, | ||
| is_delegated_to_us_on_chain: bool, | ||
| remote_slot_in_bank: u64, | ||
| deleg_record: Option<DelegationRecord>, | ||
| validator_auth: &Pubkey, | ||
| ) -> bool { | ||
| // In the case of a subscription update for an account that was undelegating | ||
| // we know that the undelegation or associated commit or possibly a previous | ||
| // commit made after we subscribed to the account was completed, otherwise | ||
| // there would be no update. | ||
| // | ||
| // Now the account could be in one the following states: | ||
| // | ||
| // A) the account was undelegated and remained so | ||
| // B) the account was undelegated and was re-delegated to us or the system | ||
| // program (broadcast account) | ||
| // C) the account was undelegated and was re-delegated to another validator | ||
| // D) the account's undelegation request did not complete. | ||
| // In case of a subscription update the commit (or a commit scheduled previously) did trigger an update. | ||
| // Alternatively someone may be accessing the account while undelegation is still pending. | ||
| // Thus the account is still delegated to us on chain. | ||
| // | ||
| // In the case of D) we want to keep the bank version of the account. | ||
| // | ||
| // In all other cases we want to clone the remote version of the account into | ||
| // our bank which will automatically set the correct delegated state and | ||
| // untoggle the undelegating flag. | ||
| if is_delegated_to_us_on_chain { | ||
| // B) or D) | ||
| // Since the account was found to be delegated we must have | ||
| // found a delegation record and thus have the delegation slot. | ||
| let delegation_slot = deleg_record | ||
| .as_ref() | ||
| .map(|d| d.delegation_slot) | ||
| .unwrap_or_default(); | ||
| if delegation_slot < remote_slot_in_bank { | ||
| // The last update of the account was after the last delegation | ||
| // Therefore the account was not redelegated which indicates | ||
| // that the undelegation is still not completed. Case (D)) | ||
| debug!( | ||
| "Undelegation for {pubkey} is still pending. Keeping bank account.", | ||
| ); | ||
| true | ||
| } else { | ||
| // This is a re-delegation to us after undelegation completed. | ||
| // Case (B)) | ||
| debug!( | ||
| "Undelegation completed for account {pubkey} and it was re-delegated to us at slot: ({delegation_slot}).", | ||
| ); | ||
| magicblock_metrics::metrics::inc_undelegation_completed(); | ||
| false | ||
| } | ||
| } else if let Some(deleg_record) = deleg_record { | ||
| // Account delegated to other (Case C)) -> clone as is | ||
| debug!( | ||
| "Account {pubkey} was undelegated and re-delegated to another validator. authority: {}, delegated_to: {}", | ||
| validator_auth, deleg_record.authority | ||
| ); | ||
| magicblock_metrics::metrics::inc_undelegation_completed(); | ||
| false | ||
| } else { | ||
| // Account no longer delegated (Case A)) -> clone as is | ||
| debug!("Account {pubkey} was undelegated and remained so"); | ||
| magicblock_metrics::metrics::inc_undelegation_completed(); | ||
| false | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use dlp::state::DelegationRecord; | ||
| use solana_pubkey::Pubkey; | ||
|
|
||
| use super::*; | ||
|
|
||
| fn create_delegation_record(delegation_slot: u64) -> DelegationRecord { | ||
| DelegationRecord { | ||
| authority: Pubkey::default(), | ||
| owner: Pubkey::default(), | ||
| delegation_slot, | ||
| lamports: 1000, | ||
| commit_frequency_ms: 100, | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_account_was_undelegated_and_remained_so() { | ||
| // Case A: The account was undelegated and remained so. | ||
| // Conditions: | ||
| // - is_delegated: false (account is not delegated to us on chain) | ||
| // - deleg_record: None (no delegation record associated) | ||
| // Expected: true (should override/clone as is) | ||
|
|
||
| let pubkey = Pubkey::default(); | ||
| let is_delegated = false; | ||
| let remote_slot = 100; | ||
| let deleg_record = None; | ||
|
|
||
| assert!(!account_still_undelegating_on_chain( | ||
| &pubkey, | ||
| is_delegated, | ||
| remote_slot, | ||
| deleg_record, | ||
| &Pubkey::default(), | ||
| )); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_account_was_undelegated_and_redelegated_to_us() { | ||
| // Case B: The account was undelegated and was re-delegated to us. | ||
| // Conditions: | ||
| // - is_delegated: true (account is delegated to us on chain) | ||
| // - delegation_slot >= remote_slot (delegation happend after we last updated the account) | ||
| // Expected: true (should override/update) | ||
|
|
||
| let pubkey = Pubkey::default(); | ||
| let is_delegated = true; | ||
| let remote_slot = 100; | ||
|
|
||
| // Subcase B1: delegation_slot == remote_slot | ||
| let delegation_slot = 100; | ||
| let deleg_record = Some(create_delegation_record(delegation_slot)); | ||
| assert!(!account_still_undelegating_on_chain( | ||
| &pubkey, | ||
| is_delegated, | ||
| remote_slot, | ||
| deleg_record, | ||
| &Pubkey::default(), | ||
| )); | ||
|
|
||
| // Subcase B2: delegation_slot > remote_slot | ||
| let delegation_slot = 101; | ||
| let deleg_record = Some(create_delegation_record(delegation_slot)); | ||
| assert!(!account_still_undelegating_on_chain( | ||
| &pubkey, | ||
| is_delegated, | ||
| remote_slot, | ||
| deleg_record, | ||
| &Pubkey::default(), | ||
| )); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_account_was_undelegated_and_redelegated_to_another() { | ||
| // Case C: The account was undelegated and then re-delegated to another validator. | ||
| // Conditions: | ||
| // - is_delegated: false (not delegated to us on chain) | ||
| // - deleg_record: Some(...) (but record exists, maybe describing delegation to another) | ||
| // Expected: true (should override/clone as is) | ||
|
|
||
| let pubkey = Pubkey::default(); | ||
| let is_delegated = false; | ||
| let remote_slot = 100; | ||
| // Value doesn't matter for this path | ||
| let delegation_slot = 90; | ||
| let deleg_record = Some(create_delegation_record(delegation_slot)); | ||
|
|
||
| assert!(!account_still_undelegating_on_chain( | ||
| &pubkey, | ||
| is_delegated, | ||
| remote_slot, | ||
| deleg_record, | ||
| &Pubkey::default(), | ||
| )); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_account_undelegation_pending() { | ||
| // Case D: The account's undelegation request did not complete. | ||
| // Conditions: | ||
| // - is_delegated: true | ||
| // - delegation_slot < remote_slot (delegation is older than remote update, implying pending undelegation) | ||
| // Expected: false (should NOT override, keep bank account) | ||
|
|
||
| let pubkey = Pubkey::default(); | ||
| let is_delegated = true; | ||
| let remote_slot = 100; | ||
| let delegation_slot = 99; | ||
| let deleg_record = Some(create_delegation_record(delegation_slot)); | ||
|
|
||
| assert!(account_still_undelegating_on_chain( | ||
| &pubkey, | ||
| is_delegated, | ||
| remote_slot, | ||
| deleg_record, | ||
| &Pubkey::default(), | ||
| )); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.