-
Notifications
You must be signed in to change notification settings - Fork 23
fix(validator_store): filter inactive validators #576
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
fix(validator_store): filter inactive validators #576
Conversation
anchor/validator_store/src/lib.rs
Outdated
// First, attempt to get the cluster normally | ||
if let Some(cluster) = state.clusters().get_by(&validator.cluster_id) { | ||
if cluster.liquidated { | ||
return Err(Error::SpecificError(SpecificError::Unsupported)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is the appropriate error to throw
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably just add a new variant in, something like ClusterLiquidated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes a critical issue where liquidated validators were still being processed by adding a check to filter out inactive validators from cluster operations. The change ensures that validators belonging to liquidated clusters are properly rejected to prevent unintended processing.
- Adds a liquidation status check for validator clusters
- Returns an error when attempting to process validators from liquidated clusters
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Can we add a test to avoid this regression from being added? |
Nice! We also should filter in |
Not sure if I understand, where to add a test? |
While I agree, right now it is really hard to test the validator store due to it's tightly coupled nature. I think it might be better to keep it in mind for when we add more tests. Alternatively, our integration test approach currently is based on local Testnets. We could add a check there where we assert that a liquidated validator does not attest |
anchor/validator_store/src/lib.rs
Outdated
.shares() | ||
.values() | ||
.filter_map(|v| filter_func(DoppelgangerStatus::SigningEnabled(v.validator_pubkey))) | ||
.filter(|public_key| self.get_validator_and_cluster(*public_key).is_ok()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach has a problem:
We try to lock (within get_validator_and_cluster
) while we already hold a lock (here by calling state
)
This can cause a deadlock, as the second lock might wait for a pending writer, which waits for the lock we're holding here first.
Instead, get the cluster here and check for liquidation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay, I understand, I meant to save from some duplication of code.
Could you explain in more detail how our integration test approach works? Where are the tests located? |
@diegomrsantos I am talking about #562. It runs a Testnet and then checks if the expected duties are performed. It seems feasible to add a liquidated cluster and check that it does not perform duties. |
But where are the tests exactly? I see only a yaml file there |
anchor/validator_store/src/lib.rs
Outdated
fn is_cluster_active(&self, validator_pubkey: &PublicKeyBytes) -> bool { | ||
let state = self.database.state(); | ||
|
||
if let Some(validator) = state.metadata().get_by(validator_pubkey) | ||
&& let Some(cluster) = state.clusters().get_by(&validator.cluster_id) | ||
{ | ||
return !cluster.liquidated; | ||
} | ||
|
||
// We did not manage to fetch the cluster | ||
false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, same issue as before:
By trying to borrow here (in line 594) while we already holding the lock through the .state()
call in voting_pubkeys
, we are at risk of a deadlock if a writer tries to acquire the lock between the two calls.
Furthermore, you can call get_by
directly with the validator pubkey - no need to go via the metadata
, as the multi index map allows access via the validator_pubkey
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks! I've got one nitpick, but that one is a matter of taste. Feel free to address or merge (by adding the ready-to-merge
label)
anchor/validator_store/src/lib.rs
Outdated
if let Some(clusters) = clusters.get_by(public_key) { | ||
return !clusters.liquidated; | ||
} | ||
false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice, exactly :)
still got one nitpick in me :P
I really like Option::is_some_and
, but that is a matter of taste
Sorry, I just noticed that this was based on |
2fac0bf
to
d2d1e46
Compare
Closing this in favour of #589 to keep a cleaner commit history |
Issue Addressed
#558
Makes sure if a cluster is liquidated that it doesn't get processed.