fix(validator_store): filter inactive validators#576
fix(validator_store): filter inactive validators#576petarjuki7 wants to merge 9 commits intosigp:release-v0.3.0from
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.
Not sure if this is the appropriate error to throw
There was a problem hiding this comment.
Should probably just add a new variant in, something like ClusterLiquidated
There was a problem hiding this comment.
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.
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.
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.
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.
dknopik
left a comment
There was a problem hiding this comment.
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.
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.