Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
52ccf23
Instrument attestation signing.
jimmygchen Dec 1, 2025
1fa3b39
Try signing with a rayon threadpool.
jimmygchen Dec 1, 2025
7f10002
Implement new interface
michaelsproul Dec 2, 2025
cbfb01d
Use new slashing protection check in att service
michaelsproul Dec 2, 2025
79cda98
Revert "Try signing with a rayon threadpool."
jimmygchen Dec 2, 2025
eb1e075
Merge remote-tracking branch 'jimmy/instrument-attestation-signing' i…
michaelsproul Dec 2, 2025
2ca463a
More traces
michaelsproul Dec 2, 2025
09a31c1
Add debug
michaelsproul Dec 2, 2025
f92feee
Extend slashing DB tests
michaelsproul Dec 2, 2025
6b4f005
Merge remote-tracking branch 'origin/unstable' into attestation-batch…
michaelsproul Dec 2, 2025
11bb5c3
Fix compilation snafu
michaelsproul Dec 2, 2025
dba872a
Remove bool in favour of explicit enum
michaelsproul Dec 3, 2025
2782fe8
Merge remote-tracking branch 'origin/unstable' into attestation-batch…
michaelsproul Dec 3, 2025
d6bcef0
Fix merge
michaelsproul Dec 3, 2025
1c58420
Move blocking I/O to dedicated thread
michaelsproul Dec 3, 2025
e5d9da6
Try frequent pruning of slashing protection data
michaelsproul Dec 3, 2025
e41b611
Try signing with a rayon threadpool.
jimmygchen Dec 1, 2025
9879bd1
Update tests
michaelsproul Dec 4, 2025
cd0bfa0
Merge branch 'unstable' into attestation-batch-new
michaelsproul Dec 10, 2025
871d922
Merge branch 'unstable' into attestation-batch-new
michaelsproul Dec 16, 2025
11a9be2
Merge branch 'unstable' into attestation-batch-new
michaelsproul Jan 11, 2026
100edf3
Merge branch 'unstable' into attestation-batch-new
michaelsproul Jan 19, 2026
6557254
Merge remote-tracking branch 'michael/frequent-pruning' into attestat…
michaelsproul Jan 20, 2026
2da3bf4
Attestation batch new refactor (#12)
michaelsproul Jan 20, 2026
e03208c
Fix HTTP API tests
michaelsproul Jan 20, 2026
e741cea
Move dangerous methods off trait
michaelsproul Jan 20, 2026
4ea23ee
Improve scary log
michaelsproul Jan 20, 2026
fe69edb
Merge remote-tracking branch 'michael/rayon-signing' into attestation…
michaelsproul Jan 21, 2026
65c2669
Fix web3signer tests
michaelsproul Jan 21, 2026
8621eb9
Fmt
michaelsproul Jan 21, 2026
bba7a11
Self review changes
michaelsproul Jan 22, 2026
713eae6
Use blocking task for DB op
michaelsproul Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/task_executor/src/rayon_pool_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub struct RayonPoolProvider {
/// By default ~25% of CPUs or a minimum of 1 thread.
low_priority_thread_pool: Arc<ThreadPool>,
/// Larger rayon thread pool for high-priority, compute-intensive tasks.
/// By default ~80% of CPUs or a minimum of 1 thread. Citical/highest
/// By default ~80% of CPUs or a minimum of 1 thread. Critical/highest
/// priority tasks should use the global pool instead.
high_priority_thread_pool: Arc<ThreadPool>,
}
Expand Down
88 changes: 69 additions & 19 deletions testing/web3signer_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,56 @@ mod tests {
}
self
}

/// Assert that a slashable attestation fails to be signed locally (empty result) and is
/// either signed or not by the web3signer rig depending on the value of
/// `web3signer_should_sign`.
///
/// The batch attestation signing API returns an empty result instead of an error for
/// slashable attestations.
pub async fn assert_slashable_attestation_should_sign<F, R>(
self,
case_name: &str,
generate_sig: F,
web3signer_should_sign: bool,
) -> Self
where
F: Fn(PublicKeyBytes, Arc<LighthouseValidatorStore<TestingSlotClock, E>>) -> R,
R: Future<Output = Result<Vec<Attestation<E>>, lighthouse_validator_store::Error>>,
{
for validator_rig in &self.validator_rigs {
let result =
generate_sig(self.validator_pubkey, validator_rig.validator_store.clone())
.await;

if !validator_rig.using_web3signer || !web3signer_should_sign {
// For local validators, slashable attestations should return an empty result
// or an error.
match result {
Ok(attestations) => {
assert!(
attestations.is_empty(),
"should not sign slashable {case_name}: expected empty result"
);
}
Err(ValidatorStoreError::Slashable(_)) => {
// Also acceptable - error indicates slashable
}
Err(e) => {
panic!("unexpected error for slashable {case_name}: {e:?}");
}
}
} else {
// Web3signer should sign (has its own slashing protection)
let attestations = result.expect("should sign slashable {case_name}");
assert!(
!attestations.is_empty(),
"web3signer should sign slashable {case_name}"
);
}
}
self
}
}

/// Get a generic, arbitrary attestation for signing.
Expand Down Expand Up @@ -605,12 +655,13 @@ mod tests {
})
.await
.assert_signatures_match("attestation", |pubkey, validator_store| async move {
let mut attestation = get_attestation();
let attestation = get_attestation();
validator_store
.sign_attestation(pubkey, 0, &mut attestation, Epoch::new(0))
.sign_attestations(vec![(pubkey, 0, attestation)])
.await
.unwrap();
attestation
.unwrap()
.pop()
.unwrap()
})
.await
.assert_signatures_match("signed_aggregate", |pubkey, validator_store| async move {
Expand Down Expand Up @@ -820,8 +871,6 @@ mod tests {
block
};

let current_epoch = Epoch::new(5);

TestingRig::new(
network,
slashing_protection_config,
Expand All @@ -830,42 +879,43 @@ mod tests {
)
.await
.assert_signatures_match("first_attestation", |pubkey, validator_store| async move {
let mut attestation = first_attestation();
let attestation = first_attestation();
validator_store
.sign_attestation(pubkey, 0, &mut attestation, current_epoch)
.sign_attestations(vec![(pubkey, 0, attestation)])
.await
.unwrap();
attestation
.unwrap()
.pop()
.unwrap()
})
.await
.assert_slashable_message_should_sign(
.assert_slashable_attestation_should_sign(
"double_vote_attestation",
move |pubkey, validator_store| async move {
let mut attestation = double_vote_attestation();
let attestation = double_vote_attestation();
validator_store
.sign_attestation(pubkey, 0, &mut attestation, current_epoch)
.sign_attestations(vec![(pubkey, 0, attestation)])
.await
},
slashable_message_should_sign,
)
.await
.assert_slashable_message_should_sign(
.assert_slashable_attestation_should_sign(
"surrounding_attestation",
move |pubkey, validator_store| async move {
let mut attestation = surrounding_attestation();
let attestation = surrounding_attestation();
validator_store
.sign_attestation(pubkey, 0, &mut attestation, current_epoch)
.sign_attestations(vec![(pubkey, 0, attestation)])
.await
},
slashable_message_should_sign,
)
.await
.assert_slashable_message_should_sign(
.assert_slashable_attestation_should_sign(
"surrounded_attestation",
move |pubkey, validator_store| async move {
let mut attestation = surrounded_attestation();
let attestation = surrounded_attestation();
validator_store
.sign_attestation(pubkey, 0, &mut attestation, current_epoch)
.sign_attestations(vec![(pubkey, 0, attestation)])
.await
},
slashable_message_should_sign,
Expand Down
44 changes: 30 additions & 14 deletions validator_client/http_api/src/tests/keystores.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1099,14 +1099,18 @@ async fn generic_migration_test(
check_keystore_import_response(&import_res, all_imported(keystores.len()));

// Sign attestations on VC1.
for (validator_index, mut attestation) in first_vc_attestations {
for (validator_index, attestation) in first_vc_attestations {
let public_key = keystore_pubkey(&keystores[validator_index]);
let current_epoch = attestation.data().target.epoch;
tester1
let safe_attestations = tester1
.validator_store
.sign_attestation(public_key, 0, &mut attestation, current_epoch)
.sign_attestations(vec![(public_key, 0, attestation.clone())])
.await
.unwrap();
assert_eq!(safe_attestations.len(), 1);
// Compare data only, ignoring signatures which are added during signing.
assert_eq!(safe_attestations[0].data(), attestation.data());
// Check that the signature is non-zero.
assert!(!safe_attestations[0].signature().is_infinity());
}

// Delete the selected keys from VC1.
Expand Down Expand Up @@ -1178,16 +1182,28 @@ async fn generic_migration_test(
check_keystore_import_response(&import_res, all_imported(import_indices.len()));

// Sign attestations on the second VC.
for (validator_index, mut attestation, should_succeed) in second_vc_attestations {
for (validator_index, attestation, should_succeed) in second_vc_attestations {
let public_key = keystore_pubkey(&keystores[validator_index]);
let current_epoch = attestation.data().target.epoch;
match tester2
let result = tester2
.validator_store
.sign_attestation(public_key, 0, &mut attestation, current_epoch)
.await
{
Ok(()) => assert!(should_succeed),
Err(e) => assert!(!should_succeed, "{:?}", e),
.sign_attestations(vec![(public_key, 0, attestation.clone())])
.await;
match result {
Ok(safe_attestations) => {
if should_succeed {
// Compare data only, ignoring signatures which are added during signing.
assert_eq!(safe_attestations.len(), 1);
assert_eq!(safe_attestations[0].data(), attestation.data());
// Check that the signature is non-zero.
assert!(!safe_attestations[0].signature().is_infinity());
} else {
assert!(safe_attestations.is_empty());
}
}
Err(_) => {
// Doppelganger protected or other error.
assert!(!should_succeed);
}
}
}
})
Expand Down Expand Up @@ -1313,10 +1329,10 @@ async fn delete_concurrent_with_signing() {

let handle = handle.spawn(async move {
for j in 0..num_attestations {
let mut att = make_attestation(j, j + 1);
let att = make_attestation(j, j + 1);
for public_key in thread_pubkeys.iter() {
let _ = validator_store
.sign_attestation(*public_key, 0, &mut att, Epoch::new(j + 1))
.sign_attestations(vec![(*public_key, 0, att.clone())])
.await;
}
}
Expand Down
1 change: 1 addition & 0 deletions validator_client/lighthouse_validator_store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ doppelganger_service = { workspace = true }
either = { workspace = true }
environment = { workspace = true }
eth2 = { workspace = true }
futures = { workspace = true }
initialized_validators = { workspace = true }
logging = { workspace = true }
parking_lot = { workspace = true }
Expand Down
Loading