Skip to content

Comments

Add message rate#402

Merged
diegomrsantos merged 13 commits intosigp:unstablefrom
diegomrsantos:message-rate
Jul 4, 2025
Merged

Add message rate#402
diegomrsantos merged 13 commits intosigp:unstablefrom
diegomrsantos:message-rate

Conversation

@diegomrsantos
Copy link
Member

@diegomrsantos diegomrsantos commented Jul 1, 2025

Issue Addressed

#371

Proposed Changes

This PR integrates a new message rate calculation into topic scoring by introducing a message_rate module and updating topic scoring configuration to use it with ChainSpec and an EthSpec generic.

  • Add message_rate.rs to compute expected message rates for gossipsub topics based on committee configurations.
  • Update TopicScoringOptions::new and topic_score_params_for_subnet to accept a ChainSpec and use calculate_message_rate_for_topic.
  • Expose the new message_rate module in scoring/mod.rs.

Additional Info

Please provide any additional information. For example, future considerations
or information useful for reviewers.

@diegomrsantos diegomrsantos requested a review from Copilot July 1, 2025 11:18
@diegomrsantos diegomrsantos self-assigned this Jul 1, 2025

This comment was marked as outdated.

@diegomrsantos diegomrsantos requested a review from dknopik July 1, 2025 16:40
@dknopik dknopik added the v0.2.0 Second testnet release label Jul 1, 2025
Comment on lines 105 to 138
/// Expected committee duties per epoch that are due to only sync committee beacon duties
fn expected_single_sc_committee_duties_per_epoch(
num_validators: usize,
slots_per_epoch: u32,
sync_committee_size: f64,
) -> f64 {
if num_validators == 0 {
return 0.0;
}

// If the committee has more validators than our limit, return the limit value
if num_validators >= MAX_VALIDATORS_PER_COMMITTEE {
return SINGLE_SC_DUTIES_LIMIT;
}

// Probability that a validator is not in sync committee
let sync_committee_probability = sync_committee_size / ETHEREUM_VALIDATORS;
let chance_of_not_being_in_sync_committee = 1.0 - sync_committee_probability;

// Probability that all validators are not in sync committee
let chance_that_all_validators_are_not_in_sync_committee =
chance_of_not_being_in_sync_committee.powf(num_validators as f64);

// Probability that at least one validator is in sync committee
let chance_of_at_least_one_validator_being_in_sync_committee =
1.0 - chance_that_all_validators_are_not_in_sync_committee;

// Expected number of slots with no attestation duty
let expected_slots_with_no_duty = slots_per_epoch as f64
- expected_committee_duties_per_epoch_due_to_attestation(num_validators, slots_per_epoch);

// Expected number of committee duties per epoch created due to only sync committee duties
chance_of_at_least_one_validator_being_in_sync_committee * expected_slots_with_no_duty
}
Copy link
Member

Choose a reason for hiding this comment

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

This is comment is not a critique of your implementation, but of the approach used by Go-SSV.

This does not make sense to me, especially for small committees. We calculate a miniscule chance here and multiply it by slots. The result might be accurate if we are considering the message rate over YEARS, but as soon as a validator in a small committee hits a sync committee duty, we easily exceed the calculated rate for several hours. So, if we get unlucky, the calculated rate is far off the actual rate.

Copy link
Member Author

Choose a reason for hiding this comment

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

We calculate a miniscule chance here

Could you please elaborate more?

Copy link
Member Author

@diegomrsantos diegomrsantos Jul 2, 2025

Choose a reason for hiding this comment

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

expected_single_committee_duties

You might be right. That's a graph generated with AI, and based on some test runs, it seems correct. I'm not sure if I understand your point, but the calculated value is always very small.

cc @MatheusFranco99

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I get it now

The helper that returns an epoch-level mean is the wrong tool for sizing a live gossipsub topic. Sync-committee traffic is a bursty, low-frequency, high-magnitude phenomenon: for most epochs it is zero, but the instant one of your validators hits the 27-hour sync-committee “lottery,” you get 32 extra messages every slot (8 192 messages in the period) — several orders of magnitude above the average.

Copy link
Member Author

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

I see your point, but this discrepancy is somewhat irrelevant. Also, the operator sending more messages than expected presents no harm. Let me walk through why I think that:

So, the total GossipSub score for a peer is

$$min(TopicCap, \sum_{t_i\in Topics} TopicWeight_{t_i} \cdot \Big( w_1(t_i) \cdot P_1(t_i) + w_2(t_i) \cdot P_2(t_i) + w_3(t_i) \cdot P_3(t_i) + w_4(t_i) \cdot P_4(t_i) \Big)\Big) + w_5P_5 + w_6P_6 + w_7P_7$$

Above, I abstracted how each counter is used, but more precisely:

  • $P_1 = min(P_{1c}, Cap)$
  • $P_2 = min(P_{2c}, Cap)$
  • $P_3 = (Threshold - P_{3c})^2$
  • $P_4 = P_{4c}^2$

Anyway, the only two positive subscores are P1 and P2, which may contribute to a maximum positive score of ~32.
Any negative score, when contributing, will decrease the final score dramatically. E.g., in case of a malicious message, $w_4 = -1280$ entirely cuts any positive contribution.

The P2 subscore increases the score of the peer based on "first seen deliveries", and that's where the function expected_single_sc_committee_duties_per_epoch will be used to help compute the topic's expected msg rate.
More precisely, letting:

  • $d$ be the decay
  • $S$ the target mesh size
  • $r$ the expected message rate

We compute the P2 cap by

$$Cap = \sum_{i = 0}^{\infty} d^i \cdot \frac{2r}{S} = \frac{2r}{S}\cdot \frac{1}{1-d}$$

Then, the weight is computed as

$$w_2 = \frac{80}{Cap}$$

where 80 is the maximum contribution of P2.

Alright, going back to expected_single_sc_committee_duties_per_epoch, the influence of it on $r$ is minimal compared to other contributions (e.g. from committee duties with attestation or aggregator duties). In an epoch with no sync committee duties, it will send a little less than expected. In an epoch with SC duties, it will send more. But sending more doesn't harm anything as the score will reach the Cap. And sending a bit less doesn't harm anything because the cap is computed taking into account an infinite contribution of epochs.

Sorry for the long text 😅
I tried to explain as clearly as possible why, from my perspective, this discrepancy is a bit irrelevant.

Note

Expected message rate may also be used for P3, which penalises the score, but it's currently unused.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks a lot for the clear explanation! I don't mind the long text at all ;)
If expected_single_sc_committee_duties_per_epoch is irrelevant in practical terms, do you think it'd be better to remove it and have less code to understand and maintain?

Comment on lines 16 to 17
const ESTIMATED_ATTESTATION_COMMITTEE_SIZE: f64 = ETHEREUM_VALIDATORS / 2048.0;
const AGGREGATOR_PROBABILITY: f64 = 16.0 / ESTIMATED_ATTESTATION_COMMITTEE_SIZE;
Copy link
Member

Choose a reason for hiding this comment

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

2048.0 is hardcoding E::MaxValidatorsPerCommittee, 16.0 is hardcoding ChainSpec::target_aggregators_per_committee.

Copy link
Member Author

Choose a reason for hiding this comment

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

@nkryuchkov these values are still hardcoded in stage, is it an issue?

Copy link
Contributor

@nkryuchkov nkryuchkov Jul 3, 2025

Choose a reason for hiding this comment

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

@diegomrsantos thanks for noticing this. It's not critical and we will unlikely ever change it in test networks, but probably we'll need to consider moving this to network config

Copy link
Contributor

Choose a reason for hiding this comment

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

@diegomrsantos diegomrsantos requested review from Copilot and dknopik July 3, 2025 17:24
@diegomrsantos diegomrsantos marked this pull request as ready for review July 3, 2025 17:24
Copy link
Contributor

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 integrates a new message rate calculation into topic scoring by introducing a message_rate module and updating topic scoring configuration to use it with ChainSpec and an EthSpec generic.

  • Add message_rate.rs to compute expected message rates for gossipsub topics based on committee configurations.
  • Update TopicScoringOptions::new and topic_score_params_for_subnet to accept a ChainSpec and use calculate_message_rate_for_topic.
  • Expose the new message_rate module in scoring/mod.rs.

Reviewed Changes

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

File Description
anchor/network/src/scoring/topic_score_config.rs Updated TopicScoringOptions::new and topic_score_params_for_subnet to call into the new message rate calculation with a ChainSpec and EthSpec generic
anchor/network/src/scoring/mod.rs Added mod message_rate; to include the new module
anchor/network/src/scoring/message_rate.rs New module implementing calculate_message_rate_for_topic, helper functions, and tests
Comments suppressed due to low confidence (1)

anchor/network/src/scoring/topic_score_config.rs:314

  • The signature for topic_score_params_for_subnet now takes a chain_spec parameter; the doc comment should be updated to mention chain_spec (and remove any outdated reference to one_epoch_duration).
/// Generate topic score parameters for a specific subnet

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.

LGTM, thanks a lot for this! Feel free to merge if there is no outstanding TODO from your point of view.

Not tested on a node yet as we'll wire it up with the next PR.

Comment on lines +31 to +33
// TODO: It depends on duties per epoch, 32 duties per epoch maps to
// 560. If the value of duties per epoch changes, this value needs
// to be adjusted (need to run Monte Carlo simulation for that number).
Copy link
Member

Choose a reason for hiding this comment

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

Good catch! Need to keep this is mind especially if the number of slots increases (though this is unlikely IMO).

@diegomrsantos diegomrsantos merged commit a43656e into sigp:unstable Jul 4, 2025
20 of 26 checks passed
diegomrsantos added a commit to diegomrsantos/anchor that referenced this pull request Sep 17, 2025
jking-aus pushed a commit to jking-aus/anchor that referenced this pull request Oct 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v0.2.0 Second testnet release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants