Skip to content

Commit 788a4b7

Browse files
committed
Optimise update_validators by decrypting key cache only when necessary (#4126)
## Title Optimise `update_validators` by decrypting key cache only when necessary ## Issue Addressed Resolves [#3968: Slow performance of validator client PATCH API with hundreds of keys](#3968) ## Proposed Changes 1. Add a check to determine if there is at least one local definition before decrypting the key cache. 2. Assign an empty `KeyCache` when all definitions are of the `Web3Signer` type. 3. Perform cache-related operations (e.g., saving the modified key cache) only if there are local definitions. ## Additional Info This PR addresses the excessive CPU usage and slow performance experienced when using the `PATCH lighthouse/validators/{pubkey}` request with a large number of keys. The issue was caused by the key cache using cryptography to decipher and cipher the cache entities every time the request was made. This operation called `scrypt`, which was very slow and required a lot of memory when there were many concurrent requests. These changes have no impact on the overall functionality but can lead to significant performance improvements when working with remote signers. Importantly, the key cache is never used when there are only `Web3Signer` definitions, avoiding the expensive operation of decrypting the key cache in such cases. Co-authored-by: Maksim Shcherbo <max.shcherbo@consensys.net>
1 parent 6bb28bc commit 788a4b7

File tree

1 file changed

+23
-4
lines changed

1 file changed

+23
-4
lines changed

validator_client/src/initialized_validators.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,23 @@ impl InitializedValidators {
989989

990990
let cache =
991991
KeyCache::open_or_create(&self.validators_dir).map_err(Error::UnableToOpenKeyCache)?;
992-
let mut key_cache = self.decrypt_key_cache(cache, &mut key_stores).await?;
992+
993+
// Check if there is at least one local definition.
994+
let has_local_definitions = self.definitions.as_slice().iter().any(|def| {
995+
matches!(
996+
def.signing_definition,
997+
SigningDefinition::LocalKeystore { .. }
998+
)
999+
});
1000+
1001+
// Only decrypt cache when there is at least one local definition.
1002+
// Decrypting cache is a very expensive operation which is never used for web3signer.
1003+
let mut key_cache = if has_local_definitions {
1004+
self.decrypt_key_cache(cache, &mut key_stores).await?
1005+
} else {
1006+
// Assign an empty KeyCache if all definitions are of the Web3Signer type.
1007+
KeyCache::new()
1008+
};
9931009

9941010
let mut disabled_uuids = HashSet::new();
9951011
for def in self.definitions.as_slice() {
@@ -1115,13 +1131,16 @@ impl InitializedValidators {
11151131
);
11161132
}
11171133
}
1118-
for uuid in disabled_uuids {
1119-
key_cache.remove(&uuid);
1134+
1135+
if has_local_definitions {
1136+
for uuid in disabled_uuids {
1137+
key_cache.remove(&uuid);
1138+
}
11201139
}
11211140

11221141
let validators_dir = self.validators_dir.clone();
11231142
let log = self.log.clone();
1124-
if key_cache.is_modified() {
1143+
if has_local_definitions && key_cache.is_modified() {
11251144
tokio::task::spawn_blocking(move || {
11261145
match key_cache.save(validators_dir) {
11271146
Err(e) => warn!(

0 commit comments

Comments
 (0)