Skip to content

Duty logic validation for consensus messages #228

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 40 commits into from
Apr 25, 2025

Conversation

diegomrsantos
Copy link
Contributor

@diegomrsantos diegomrsantos commented Apr 3, 2025

Issue Addressed

#161

Proposed Changes

This PR adds duty validation logic for SSV messages and integrates a new duties management module.

  • Introduced a new duties module and duties_tracker for validating sync committee and proposer duties messages.

Additional Info

This is pending #277 and will be implemented in a future PR.

@diegomrsantos diegomrsantos requested a review from Copilot April 15, 2025 16:39
Copy link
Contributor

@Copilot Copilot AI left a 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 refactors duty logic validations by introducing new parameters and modules to support BeaconNetwork and Duties tracking while updating the Validator to use a DutiesProvider.

  • Introduces the new slots_per_epoch field into validation contexts and tests.
  • Refactors the Validator to take a DutiesProvider along with a BeaconNetwork.
  • Adds new modules for duties tracking and beacon network functionalities.

Reviewed Changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated no comments.

Show a summary per file
File Description
anchor/message_validator/src/partial_signature.rs Added slots_per_epoch field in test contexts for partial signature validation.
anchor/message_validator/src/lib.rs Updated Validator struct and validation functions to use BeaconNetwork and DutiesProvider.
anchor/message_validator/src/consensus_message.rs Updated consensus message validation to utilize the new duty logic and error variants.
anchor/message_validator/src/duties/* New modules that implement duties tracking and the DutiesProvider trait.
Other files Updates updating generics and dependencies across message sender/receiver and client to support the new duty logic features.
Files not reviewed (1)
  • .githooks/pre-commit: Language not supported
Comments suppressed due to low confidence (2)

anchor/message_validator/src/partial_signature.rs:237

  • [nitpick] Consider replacing the hard-coded magic number '32' with a named constant to improve clarity and maintainability in test configurations.
+            slots_per_epoch: 32,

anchor/message_validator/src/consensus_message.rs:51

  • [nitpick] Cloning the entire slot clock might incur unnecessary performance overhead if the underlying implementation is not lightweight; consider passing a reference or ensuring that the SlotClock type implements a cheap clone.
beacon_network.slot_clock().clone(),

@diegomrsantos diegomrsantos marked this pull request as ready for review April 17, 2025 13:25
# Conflicts:
#	anchor/client/src/lib.rs
#	anchor/message_receiver/src/manager.rs
@diegomrsantos diegomrsantos requested a review from Copilot April 17, 2025 17:54
Copy link
Contributor

@Copilot Copilot AI left a 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 refactors the duty validation logic for SSV messages to improve clarity and correctness, while also integrating a new duties management module.

  • Updated validation functions to accept a generic SlotClock and DutiesProvider.
  • Introduced a new duties module and duties_tracker for handling sync committee and proposer duties.
  • Modified the Validator and related methods across message validation, sending, and receiving to propagate the new duties provider.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
anchor/message_validator/src/partial_signature.rs Updated validation context to include a generic SlotClock; minor adjustments for new duty fields.
anchor/message_validator/src/lib.rs Modified ValidationContext and Validator to include DutiesProvider; updated message validation functions accordingly.
anchor/message_validator/src/duties/mod.rs Introduced new duties module with types and traits for duty management.
anchor/message_validator/src/duties/duties_tracker.rs Added duties tracker to poll sync committee and proposer duties from beacon nodes.
anchor/message_validator/src/consensus_message.rs Integrated duty-based validation logic within consensus messages; adjusted round and timing validations.
Other files (message_sender, message_receiver, database, client, etc.) Updated generics and dependencies to accommodate the new duties provider integration.
Comments suppressed due to low confidence (1)

anchor/message_validator/src/lib.rs:302

  • Verify that substituting slot_clock with duties_provider in the call to validate_consensus_message fully preserves the original timing logic and that duties_provider properly encapsulates the necessary timing behavior.
self.duties_provider.clone(),

Comment on lines +395 to +396
// - duty's starting slot + 34 (committee and aggregation)
// - duty's starting slot + 3 (other types)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dknopik, any idea why these numbers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The committee duty is needed for attestation, which can be submitted on chain until the end of the next epoch. So a large window for message validity is a good idea here. Same reasoning for attestation aggregation.

let validator_index_count = validator_indices.len() as u64;
let slots_per_epoch_val = validation_context.slots_per_epoch;

// Skip duty search if validators * 2 exceeds slots per epoch
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Zacholme7 @dknopik, does this make sense? If the number of validators in the committee is >= slots per epoch, it doesn't mean one of them is in the sync committee. Additionally, do they use the Committee role for the sync committee? What's the difference to SyncCommittee?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Committee Role is used for BeaconVote data, which includes the current head. This is needed for attestations AND sync committee contributions.

The word "Committee" in the "Committee role" refers to the SSV committee, not the sync committee, as this role is executed once per SSV committee, not once per validator.

As for the check:

The limit is at most slots_per_epoch because we decide a BeaconVote at most once per slot.
We need to vote for a BeaconVote if

  • at least one validator is in the sync committee, or
  • at least one validator attests in this slot

Furthermore, validators attest once per epoch. So, if we have 32 validators, it is possible that we have to perform the Committee role every slot - regardless of whether one of them is in the sync committee.

That being said, I am not sure why we multiply/divide by two.

slot: Slot,
validator_indices: &[ValidatorIndex],
duty_provider: Arc<impl DutiesProvider>,
) -> Result<(u64, bool), ValidationFailure> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use a (u64, bool) instead of a Option<u64>?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}
}
}
Ok(Some(std::cmp::min(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@nkryuchkov given the if at line 580, it seems when we get here, 2 * validator_index_count >= slots_per_epoch_val, so we could simply return slots_per_epoch_val?

Copy link
Contributor

@nkryuchkov nkryuchkov Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When validator_index_count < slots_per_epoch_val / 2, the code inside the if returns only if one of the indices satisfies the duty_provider.is_validator_in_sync_committee(period, index) condition, so it can also go out of the if block and reach the last line. To get rid of min, we'd need to add an extra check:

		// Skip duty search if validators * 2 exceeds slots per epoch,
		// as the maximum duties per epoch is capped at the number of slots.
		// This avoids unnecessary checks.
		if validatorIndexCount*2 >= slotsPerEpoch {
			return slotsPerEpoch, true
		}
		// Check if there is at least one validator in the sync committee.
		// If so, the duty limit is equal to the number of slots per epoch.
		period := mv.netCfg.Beacon.EstimatedSyncCommitteePeriodAtEpoch(mv.netCfg.Beacon.EstimatedEpochAtSlot(slot))
		for _, i := range validatorIndices {
			if mv.dutyStore.SyncCommittee.Duty(period, i) != nil {
				return slotsPerEpoch, true
			}
		}

		return 2 * validatorIndexCount, true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I like your solution more. But do we need to skip duty_provider.is_validator_in_sync_committee(period, index) tho? Isn't it just a map get?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@diegomrsantos I'm not sure I understand, what do you mean by skipping? Yes, it's just a map get

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the code below necessary?

if validatorIndexCount*2 >= slotsPerEpoch {
	return slotsPerEpoch, true
}

Copy link
Contributor Author

@diegomrsantos diegomrsantos Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the *2 in the condition validator_index_count * 2 >= slots_per_epoch_val? If we want to skip the check, shouldn't it be validator_index_count >= slots_per_epoch_val?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@diegomrsantos sorry for the delay. The *2 is because there might be 2 duties per validator. Do I understand correctly that your question is only about the *2 part?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nkryuchkov I assume you mean sync committee and attestation duty. But isn't the BeaconData only voted on once per committee regardless?

Copy link
Contributor

@nkryuchkov nkryuchkov Apr 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dknopik IIRC the main reason was reorgs. @MatheusFranco99 can you please confirm it? If one of validators participates in sync committee, the duty limit changes to slotsPerEpoch

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nkryuchkov @dknopik
Yes, the reason is reorgs. We allow up to 2 attestation duties per epoch, i.e. 2*validatorIndexCount. For example, a committee with a single validator is allowed to send messages for up to 2 duties.
However, for the case validatorIndexCount*2 >= slotsPerEpoch, it's meaningless to set the maximum number of committee duties to more than 32, so we cap it to 32.

The only exception is when one of the committee's validators is in the sync committee period. In this case, the committee can send msgs for up to 32 duties, i.e. all slots.

@diegomrsantos Answering your question, yes, the following code is equivalent:

period := mv.netCfg.Beacon.EstimatedSyncCommitteePeriodAtEpoch(mv.netCfg.Beacon.EstimatedEpochAtSlot(slot))
for _, i := range validatorIndices {
    if mv.dutyStore.SyncCommittee.Duty(period, i) != nil {
        return slotsPerEpoch, true
    }
}
return min(validatorIndexCount*2, slotsPerEpoch), true

We added the IF check before mv.dutyStore.SyncCommittee.Duty is called because:

  • it's very likely that validatorIndexCount*2 >= slotsPerEpoch, immediately returning.
  • and it's very unlikely that any mv.dutyStore.SyncCommittee.Duty will return true, so the FOR loop complexity is expected to be len(validatorIndices)

@diegomrsantos diegomrsantos added the ready-for-review This PR is ready to be reviewed label Apr 22, 2025
Copy link
Contributor

@Copilot Copilot AI left a 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 introduces duty validation logic for SSV messages and integrates a new duties management module into the message validation and networking components. The key changes include:

  • The addition of a new duties module and a duties_tracker to manage sync committee and proposer duties.
  • Updates to the Validator and related message validation functions to accept a generic DutiesProvider, along with corresponding test updates.
  • Modifications in networking and message receiver code to propagate the new generic parameters and improve logging.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
anchor/message_validator/src/partial_signature.rs Updated function signatures and tests to use a generic SlotClock type.
anchor/message_validator/src/lib.rs Integrated DutiesProvider into Validator and refactored validation logic.
anchor/message_validator/src/duties/mod.rs Added the new duties module for sync committee and proposer duties.
anchor/message_validator/src/duties/duties_tracker.rs Introduced duties_tracker with polling and pruning of duties.
anchor/message_validator/src/consensus_state.rs Added getters for max_slot and duty counts for operators.
anchor/message_validator/src/consensus_message.rs Updated consensus message validation to incorporate duty-based checks.
anchor/message_sender/src/network.rs Updated network sender to use the new generic Validator with DutiesProvider.
anchor/message_receiver/src/manager.rs Updated message receiver to use generic Validator and improved logging.
anchor/database/src/state.rs Added a helper to collect validator indices.
anchor/common/ssv_types/src/cluster.rs Introduced conversion from ValidatorIndex to u64.
anchor/client/src/lib.rs Updated client startup to initialize and start the new DutiesTracker.

@diegomrsantos diegomrsantos requested a review from jking-aus April 23, 2025 17:18
@diegomrsantos diegomrsantos changed the title Duty logic validation Duty logic validation for consensus messages Apr 25, 2025
Copy link
Member

@dknopik dknopik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work, LGTM! sorry for the delay

@dknopik dknopik merged commit 26f4a8b into sigp:unstable Apr 25, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready-for-review This PR is ready to be reviewed validation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants