mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-23 21:15:41 +00:00
grandpa: restrict grandpa gossip (#9131)
* grandpa: make gossip more conservative (and fair) * grandpa: make round commit timer dependent on gossip_duration * grandpa: add gossip tests * grandpa: reduce variance in tests
This commit is contained in:
@@ -110,12 +110,23 @@ const CATCH_UP_PROCESS_TIMEOUT: Duration = Duration::from_secs(30);
|
|||||||
/// catch up request.
|
/// catch up request.
|
||||||
const CATCH_UP_THRESHOLD: u64 = 2;
|
const CATCH_UP_THRESHOLD: u64 = 2;
|
||||||
|
|
||||||
const PROPAGATION_ALL: u32 = 4; //in rounds;
|
/// The total round duration measured in periods of gossip duration:
|
||||||
const PROPAGATION_ALL_AUTHORITIES: u32 = 2; //in rounds;
|
/// 2 gossip durations for prevote timer
|
||||||
const PROPAGATION_SOME_NON_AUTHORITIES: u32 = 3; //in rounds;
|
/// 2 gossip durations for precommit timer
|
||||||
const ROUND_DURATION: u32 = 2; // measured in gossip durations
|
/// 1 gossip duration for precommits to spread
|
||||||
|
const ROUND_DURATION: u32 = 5;
|
||||||
|
|
||||||
const MIN_LUCKY: usize = 5;
|
/// The period, measured in rounds, since the latest round start, after which we will start
|
||||||
|
/// propagating gossip messages to more nodes than just the lucky ones.
|
||||||
|
const PROPAGATION_SOME: f32 = 1.5;
|
||||||
|
|
||||||
|
/// The period, measured in rounds, since the latest round start, after which we will start
|
||||||
|
/// propagating gossip messages to all the nodes we are connected to.
|
||||||
|
const PROPAGATION_ALL: f32 = 3.0;
|
||||||
|
|
||||||
|
/// Assuming a network of 3000 nodes, using a fanout of 4, after about 6 iterations
|
||||||
|
/// of gossip a message has very likely reached all nodes on the network (`log4(3000)`).
|
||||||
|
const LUCKY_PEERS: usize = 4;
|
||||||
|
|
||||||
type Report = (PeerId, ReputationChange);
|
type Report = (PeerId, ReputationChange);
|
||||||
|
|
||||||
@@ -459,6 +470,7 @@ impl Misbehavior {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct PeerInfo<N> {
|
struct PeerInfo<N> {
|
||||||
view: View<N>,
|
view: View<N>,
|
||||||
roles: ObservedRole,
|
roles: ObservedRole,
|
||||||
@@ -473,19 +485,27 @@ impl<N> PeerInfo<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The peers we're connected do in gossip.
|
/// The peers we're connected to in gossip.
|
||||||
struct Peers<N> {
|
struct Peers<N> {
|
||||||
inner: HashMap<PeerId, PeerInfo<N>>,
|
inner: HashMap<PeerId, PeerInfo<N>>,
|
||||||
lucky_peers: HashSet<PeerId>,
|
/// The randomly picked set of `LUCKY_PEERS` we'll gossip to in the first stage of round
|
||||||
lucky_authorities: HashSet<PeerId>,
|
/// gossiping.
|
||||||
|
first_stage_peers: HashSet<PeerId>,
|
||||||
|
/// The randomly picked set of peers we'll gossip to in the second stage of gossiping if the
|
||||||
|
/// first stage didn't allow us to spread the voting data enough to conclude the round. This set
|
||||||
|
/// should have size `sqrt(connected_peers)`.
|
||||||
|
second_stage_peers: HashSet<PeerId>,
|
||||||
|
/// The randomly picked set of `LUCKY_PEERS` light clients we'll gossip commit messages to.
|
||||||
|
lucky_light_peers: HashSet<PeerId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N> Default for Peers<N> {
|
impl<N> Default for Peers<N> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Peers {
|
Peers {
|
||||||
inner: HashMap::new(),
|
inner: HashMap::new(),
|
||||||
lucky_peers: HashSet::new(),
|
first_stage_peers: HashSet::new(),
|
||||||
lucky_authorities: HashSet::new(),
|
second_stage_peers: HashSet::new(),
|
||||||
|
lucky_light_peers: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,14 +513,18 @@ impl<N> Default for Peers<N> {
|
|||||||
impl<N: Ord> Peers<N> {
|
impl<N: Ord> Peers<N> {
|
||||||
fn new_peer(&mut self, who: PeerId, role: ObservedRole) {
|
fn new_peer(&mut self, who: PeerId, role: ObservedRole) {
|
||||||
match role {
|
match role {
|
||||||
ObservedRole::Authority if self.lucky_authorities.len() < MIN_LUCKY => {
|
ObservedRole::Authority if self.first_stage_peers.len() < LUCKY_PEERS => {
|
||||||
self.lucky_authorities.insert(who.clone());
|
self.first_stage_peers.insert(who.clone());
|
||||||
}
|
}
|
||||||
ObservedRole::Full if self.lucky_peers.len() < MIN_LUCKY => {
|
ObservedRole::Authority if self.second_stage_peers.len() < LUCKY_PEERS => {
|
||||||
self.lucky_peers.insert(who.clone());
|
self.second_stage_peers.insert(who.clone());
|
||||||
|
}
|
||||||
|
ObservedRole::Light if self.lucky_light_peers.len() < LUCKY_PEERS => {
|
||||||
|
self.lucky_light_peers.insert(who.clone());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inner.insert(who, PeerInfo::new(role));
|
self.inner.insert(who, PeerInfo::new(role));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,14 +532,17 @@ impl<N: Ord> Peers<N> {
|
|||||||
self.inner.remove(who);
|
self.inner.remove(who);
|
||||||
// This does not happen often enough compared to round duration,
|
// This does not happen often enough compared to round duration,
|
||||||
// so we don't reshuffle.
|
// so we don't reshuffle.
|
||||||
self.lucky_peers.remove(who);
|
self.first_stage_peers.remove(who);
|
||||||
self.lucky_authorities.remove(who);
|
self.second_stage_peers.remove(who);
|
||||||
|
self.lucky_light_peers.remove(who);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a reference to the new view, if the peer is known.
|
// returns a reference to the new view, if the peer is known.
|
||||||
fn update_peer_state(&mut self, who: &PeerId, update: NeighborPacket<N>)
|
fn update_peer_state(
|
||||||
-> Result<Option<&View<N>>, Misbehavior>
|
&mut self,
|
||||||
{
|
who: &PeerId,
|
||||||
|
update: NeighborPacket<N>,
|
||||||
|
) -> Result<Option<&View<N>>, Misbehavior> {
|
||||||
let peer = match self.inner.get_mut(who) {
|
let peer = match self.inner.get_mut(who) {
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
@@ -563,69 +590,93 @@ impl<N: Ord> Peers<N> {
|
|||||||
self.inner.get(who)
|
self.inner.get(who)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connected_authorities(&self) -> usize {
|
|
||||||
self.inner
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, info)| matches!(info.roles, ObservedRole::Authority))
|
|
||||||
.count()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connected_full(&self) -> usize {
|
|
||||||
self.inner
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, info)| matches!(info.roles, ObservedRole::Full))
|
|
||||||
.count()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reshuffle(&mut self) {
|
fn reshuffle(&mut self) {
|
||||||
let mut lucky_peers: Vec<_> = self
|
// we want to randomly select peers into three sets according to the following logic:
|
||||||
.inner
|
// - first set: LUCKY_PEERS random peers where at least LUCKY_PEERS/2 are authorities (unless
|
||||||
.iter()
|
// we're not connected to that many authorities)
|
||||||
.filter_map(|(id, info)| {
|
// - second set: max(LUCKY_PEERS, sqrt(peers)) peers where at least LUCKY_PEERS are authorities.
|
||||||
if matches!(info.roles, ObservedRole::Full) {
|
// - third set: LUCKY_PEERS random light client peers
|
||||||
Some(id.clone())
|
|
||||||
|
let shuffled_peers = {
|
||||||
|
let mut peers = self
|
||||||
|
.inner
|
||||||
|
.iter()
|
||||||
|
.map(|(peer_id, info)| (peer_id.clone(), info.clone()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
peers.shuffle(&mut rand::thread_rng());
|
||||||
|
peers
|
||||||
|
};
|
||||||
|
|
||||||
|
let shuffled_authorities = shuffled_peers.iter().filter_map(|(peer_id, info)| {
|
||||||
|
if matches!(info.roles, ObservedRole::Authority) {
|
||||||
|
Some(peer_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut first_stage_peers = HashSet::new();
|
||||||
|
let mut second_stage_peers = HashSet::new();
|
||||||
|
|
||||||
|
// we start by allocating authorities to the first stage set and when the minimum of
|
||||||
|
// `LUCKY_PEERS / 2` is filled we start allocating to the second stage set.
|
||||||
|
let half_lucky = LUCKY_PEERS / 2;
|
||||||
|
let one_and_a_half_lucky = LUCKY_PEERS + half_lucky;
|
||||||
|
let mut n_authorities_added = 0;
|
||||||
|
for peer_id in shuffled_authorities {
|
||||||
|
if n_authorities_added < half_lucky {
|
||||||
|
first_stage_peers.insert(peer_id.clone());
|
||||||
|
} else if n_authorities_added < one_and_a_half_lucky {
|
||||||
|
second_stage_peers.insert(peer_id.clone());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
n_authorities_added += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill up first and second sets with remaining peers (either full or authorities)
|
||||||
|
// prioritizing filling the first set over the second.
|
||||||
|
let n_second_stage_peers = LUCKY_PEERS.max((shuffled_peers.len() as f32).sqrt() as usize);
|
||||||
|
for (peer_id, info) in &shuffled_peers {
|
||||||
|
if info.roles.is_light() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if first_stage_peers.len() < LUCKY_PEERS {
|
||||||
|
first_stage_peers.insert(peer_id.clone());
|
||||||
|
second_stage_peers.remove(peer_id);
|
||||||
|
} else if second_stage_peers.len() < n_second_stage_peers {
|
||||||
|
if !first_stage_peers.contains(peer_id) {
|
||||||
|
second_stage_peers.insert(peer_id.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pick `LUCKY_PEERS` random light peers
|
||||||
|
let lucky_light_peers = shuffled_peers
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(peer_id, info)| {
|
||||||
|
if info.roles.is_light() {
|
||||||
|
Some(peer_id)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.take(LUCKY_PEERS)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut lucky_authorities: Vec<_> = self
|
self.first_stage_peers = first_stage_peers;
|
||||||
.inner
|
self.second_stage_peers = second_stage_peers;
|
||||||
.iter()
|
self.lucky_light_peers = lucky_light_peers;
|
||||||
.filter_map(|(id, info)| {
|
|
||||||
if matches!(info.roles, ObservedRole::Authority) {
|
|
||||||
Some(id.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let num_non_authorities = ((lucky_peers.len() as f32).sqrt() as usize)
|
|
||||||
.max(MIN_LUCKY)
|
|
||||||
.min(lucky_peers.len());
|
|
||||||
|
|
||||||
let num_authorities = ((lucky_authorities.len() as f32).sqrt() as usize)
|
|
||||||
.max(MIN_LUCKY)
|
|
||||||
.min(lucky_authorities.len());
|
|
||||||
|
|
||||||
lucky_peers.partial_shuffle(&mut rand::thread_rng(), num_non_authorities);
|
|
||||||
lucky_peers.truncate(num_non_authorities);
|
|
||||||
|
|
||||||
lucky_authorities.partial_shuffle(&mut rand::thread_rng(), num_authorities);
|
|
||||||
lucky_authorities.truncate(num_authorities);
|
|
||||||
|
|
||||||
self.lucky_peers.clear();
|
|
||||||
self.lucky_peers.extend(lucky_peers.into_iter());
|
|
||||||
|
|
||||||
self.lucky_authorities.clear();
|
|
||||||
self.lucky_authorities.extend(lucky_authorities.into_iter());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub(super) enum Action<H> {
|
pub(super) enum Action<H> {
|
||||||
// repropagate under given topic, to the given peers, applying cost/benefit to originator.
|
// repropagate under given topic, to the given peers, applying cost/benefit to originator.
|
||||||
Keep(H, ReputationChange),
|
Keep(H, ReputationChange),
|
||||||
// discard and process.
|
// discard and process.
|
||||||
@@ -1182,76 +1233,40 @@ impl<Block: BlockT> Inner<Block> {
|
|||||||
/// The initial logic for filtering round messages follows the given state
|
/// The initial logic for filtering round messages follows the given state
|
||||||
/// transitions:
|
/// transitions:
|
||||||
///
|
///
|
||||||
/// - State 0: not allowed to anyone (only if our local node is not an authority)
|
/// - State 1: allowed to LUCKY_PEERS random peers (where at least LUCKY_PEERS/2 are authorities)
|
||||||
/// - State 1: allowed to random `sqrt(authorities)`
|
/// - State 2: allowed to max(LUCKY_PEERS, sqrt(random peers)) (where at least LUCKY_PEERS are authorities)
|
||||||
/// - State 2: allowed to all authorities
|
/// - State 3: allowed to all peers
|
||||||
/// - State 3: allowed to random `sqrt(non-authorities)`
|
|
||||||
/// - State 4: allowed to all non-authorities
|
|
||||||
///
|
///
|
||||||
/// Transitions will be triggered on repropagation attempts by the
|
/// Transitions will be triggered on repropagation attempts by the underlying gossip layer.
|
||||||
/// underlying gossip layer, which should happen every 30 seconds.
|
fn round_message_allowed(&self, who: &PeerId) -> bool {
|
||||||
fn round_message_allowed<N>(&self, who: &PeerId, peer: &PeerInfo<N>) -> bool {
|
|
||||||
let round_duration = self.config.gossip_duration * ROUND_DURATION;
|
let round_duration = self.config.gossip_duration * ROUND_DURATION;
|
||||||
let round_elapsed = match self.local_view {
|
let round_elapsed = match self.local_view {
|
||||||
Some(ref local_view) => local_view.round_start.elapsed(),
|
Some(ref local_view) => local_view.round_start.elapsed(),
|
||||||
None => return false,
|
None => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if !self.config.local_role.is_authority()
|
if self.config.local_role.is_light() {
|
||||||
&& round_elapsed < round_duration * PROPAGATION_ALL
|
|
||||||
{
|
|
||||||
// non-authority nodes don't gossip any messages right away. we
|
|
||||||
// assume that authorities (and sentries) are strongly connected, so
|
|
||||||
// it should be unnecessary for non-authorities to gossip all
|
|
||||||
// messages right away.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
match peer.roles {
|
if round_elapsed < round_duration.mul_f32(PROPAGATION_SOME) {
|
||||||
ObservedRole::Authority => {
|
self.peers.first_stage_peers.contains(who)
|
||||||
let authorities = self.peers.connected_authorities();
|
} else if round_elapsed < round_duration.mul_f32(PROPAGATION_ALL) {
|
||||||
|
self.peers.first_stage_peers.contains(who)
|
||||||
// the target node is an authority, on the first round duration we start by
|
|| self.peers.second_stage_peers.contains(who)
|
||||||
// sending the message to only `sqrt(authorities)` (if we're
|
} else {
|
||||||
// connected to at least `MIN_LUCKY`).
|
self.peers
|
||||||
if round_elapsed < round_duration * PROPAGATION_ALL_AUTHORITIES
|
.peer(who)
|
||||||
&& authorities > MIN_LUCKY
|
.map(|info| !info.roles.is_light())
|
||||||
{
|
.unwrap_or(false)
|
||||||
self.peers.lucky_authorities.contains(who)
|
|
||||||
} else {
|
|
||||||
// otherwise we already went through the step above, so
|
|
||||||
// we won't filter the message and send it to all
|
|
||||||
// authorities for whom it is polite to do so
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ObservedRole::Full => {
|
|
||||||
// the node is not an authority so we apply stricter filters
|
|
||||||
if round_elapsed >= round_duration * PROPAGATION_ALL {
|
|
||||||
// if we waited for 3 (or more) rounds
|
|
||||||
// then it is allowed to be sent to all peers.
|
|
||||||
true
|
|
||||||
} else if round_elapsed >= round_duration * PROPAGATION_SOME_NON_AUTHORITIES {
|
|
||||||
// otherwise we only send it to `sqrt(non-authorities)`.
|
|
||||||
self.peers.lucky_peers.contains(who)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ObservedRole::Light => {
|
|
||||||
// we never gossip round messages to light clients as they don't
|
|
||||||
// participate in the full grandpa protocol
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The initial logic for filtering global messages follows the given state
|
/// The initial logic for filtering global messages follows the given state
|
||||||
/// transitions:
|
/// transitions:
|
||||||
///
|
///
|
||||||
/// - State 0: send to `sqrt(authorities)` ++ `sqrt(non-authorities)`.
|
/// - State 1: allowed to max(LUCKY_PEERS, sqrt(peers)) (where at least LUCKY_PEERS are authorities)
|
||||||
/// - State 1: send to all authorities
|
/// - State 2: allowed to all peers
|
||||||
/// - State 2: send to all non-authorities
|
|
||||||
///
|
///
|
||||||
/// We are more lenient with global messages since there should be a lot
|
/// We are more lenient with global messages since there should be a lot
|
||||||
/// less global messages than round messages (just commits), and we want
|
/// less global messages than round messages (just commits), and we want
|
||||||
@@ -1260,49 +1275,23 @@ impl<Block: BlockT> Inner<Block> {
|
|||||||
///
|
///
|
||||||
/// Transitions will be triggered on repropagation attempts by the
|
/// Transitions will be triggered on repropagation attempts by the
|
||||||
/// underlying gossip layer, which should happen every 30 seconds.
|
/// underlying gossip layer, which should happen every 30 seconds.
|
||||||
fn global_message_allowed<N>(&self, who: &PeerId, peer: &PeerInfo<N>) -> bool {
|
fn global_message_allowed(&self, who: &PeerId) -> bool {
|
||||||
let round_duration = self.config.gossip_duration * ROUND_DURATION;
|
let round_duration = self.config.gossip_duration * ROUND_DURATION;
|
||||||
let round_elapsed = match self.local_view {
|
let round_elapsed = match self.local_view {
|
||||||
Some(ref local_view) => local_view.round_start.elapsed(),
|
Some(ref local_view) => local_view.round_start.elapsed(),
|
||||||
None => return false,
|
None => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
match peer.roles {
|
if self.config.local_role.is_light() {
|
||||||
ObservedRole::Authority => {
|
return false;
|
||||||
let authorities = self.peers.connected_authorities();
|
}
|
||||||
|
|
||||||
// the target node is an authority, on the first round duration we start by
|
if round_elapsed < round_duration.mul_f32(PROPAGATION_ALL) {
|
||||||
// sending the message to only `sqrt(authorities)` (if we're
|
self.peers.first_stage_peers.contains(who)
|
||||||
// connected to at least `MIN_LUCKY`).
|
|| self.peers.second_stage_peers.contains(who)
|
||||||
if round_elapsed < round_duration * PROPAGATION_ALL_AUTHORITIES
|
|| self.peers.lucky_light_peers.contains(who)
|
||||||
&& authorities > MIN_LUCKY
|
} else {
|
||||||
{
|
true
|
||||||
self.peers.lucky_authorities.contains(who)
|
|
||||||
} else {
|
|
||||||
// otherwise we already went through the step above, so
|
|
||||||
// we won't filter the message and send it to all
|
|
||||||
// authorities for whom it is polite to do so
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ObservedRole::Full | ObservedRole::Light => {
|
|
||||||
let non_authorities = self.peers.connected_full();
|
|
||||||
|
|
||||||
// the target node is not an authority, on the first and second
|
|
||||||
// round duration we start by sending the message to only
|
|
||||||
// `sqrt(non_authorities)` (if we're connected to at least
|
|
||||||
// `MIN_LUCKY`).
|
|
||||||
if round_elapsed < round_duration * PROPAGATION_SOME_NON_AUTHORITIES
|
|
||||||
&& non_authorities > MIN_LUCKY
|
|
||||||
{
|
|
||||||
self.peers.lucky_peers.contains(who)
|
|
||||||
} else {
|
|
||||||
// otherwise we already went through the step above, so
|
|
||||||
// we won't filter the message and send it to all
|
|
||||||
// non-authorities for whom it is polite to do so
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1529,9 +1518,12 @@ impl<Block: BlockT> sc_network_gossip::Validator<Block> for GossipValidator<Bloc
|
|||||||
self.inner.write().peers.peer_disconnected(who);
|
self.inner.write().peers.peer_disconnected(who);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, context: &mut dyn ValidatorContext<Block>, who: &PeerId, data: &[u8])
|
fn validate(
|
||||||
-> sc_network_gossip::ValidationResult<Block::Hash>
|
&self,
|
||||||
{
|
context: &mut dyn ValidatorContext<Block>,
|
||||||
|
who: &PeerId,
|
||||||
|
data: &[u8],
|
||||||
|
) -> sc_network_gossip::ValidationResult<Block::Hash> {
|
||||||
let (action, broadcast_topics, peer_reply) = self.do_validate(who, data);
|
let (action, broadcast_topics, peer_reply) = self.do_validate(who, data);
|
||||||
|
|
||||||
// not with lock held!
|
// not with lock held!
|
||||||
@@ -1560,9 +1552,9 @@ impl<Block: BlockT> sc_network_gossip::Validator<Block> for GossipValidator<Bloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message_allowed<'a>(&'a self)
|
fn message_allowed<'a>(
|
||||||
-> Box<dyn FnMut(&PeerId, MessageIntent, &Block::Hash, &[u8]) -> bool + 'a>
|
&'a self,
|
||||||
{
|
) -> Box<dyn FnMut(&PeerId, MessageIntent, &Block::Hash, &[u8]) -> bool + 'a> {
|
||||||
let (inner, do_rebroadcast) = {
|
let (inner, do_rebroadcast) = {
|
||||||
use parking_lot::RwLockWriteGuard;
|
use parking_lot::RwLockWriteGuard;
|
||||||
|
|
||||||
@@ -1598,12 +1590,12 @@ impl<Block: BlockT> sc_network_gossip::Validator<Block> for GossipValidator<Bloc
|
|||||||
|
|
||||||
if let MessageIntent::Broadcast = intent {
|
if let MessageIntent::Broadcast = intent {
|
||||||
if maybe_round.is_some() {
|
if maybe_round.is_some() {
|
||||||
if !inner.round_message_allowed(who, peer) {
|
if !inner.round_message_allowed(who) {
|
||||||
// early return if the vote message isn't allowed at this stage.
|
// early return if the vote message isn't allowed at this stage.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !inner.global_message_allowed(who, peer) {
|
if !inner.global_message_allowed(who) {
|
||||||
// early return if the global message isn't allowed at this stage.
|
// early return if the global message isn't allowed at this stage.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2391,7 +2383,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn progressively_gossips_to_more_peers() {
|
fn progressively_gossips_to_more_peers_as_round_duration_increases() {
|
||||||
let mut config = config();
|
let mut config = config();
|
||||||
config.gossip_duration = Duration::from_secs(300); // Set to high value to prevent test race
|
config.gossip_duration = Duration::from_secs(300); // Set to high value to prevent test race
|
||||||
let round_duration = config.gossip_duration * ROUND_DURATION;
|
let round_duration = config.gossip_duration * ROUND_DURATION;
|
||||||
@@ -2414,14 +2406,26 @@ mod tests {
|
|||||||
full_nodes.resize_with(30, || PeerId::random());
|
full_nodes.resize_with(30, || PeerId::random());
|
||||||
|
|
||||||
for i in 0..30 {
|
for i in 0..30 {
|
||||||
val.inner.write().peers.new_peer(authorities[i].clone(), ObservedRole::Authority);
|
val.inner
|
||||||
val.inner.write().peers.new_peer(full_nodes[i].clone(), ObservedRole::Full);
|
.write()
|
||||||
|
.peers
|
||||||
|
.new_peer(authorities[i].clone(), ObservedRole::Authority);
|
||||||
|
|
||||||
|
val.inner
|
||||||
|
.write()
|
||||||
|
.peers
|
||||||
|
.new_peer(full_nodes[i].clone(), ObservedRole::Full);
|
||||||
}
|
}
|
||||||
|
|
||||||
let test = |num_round, peers| {
|
let test = |rounds_elapsed, peers| {
|
||||||
// rewind n round durations
|
// rewind n round durations
|
||||||
val.inner.write().local_view.as_mut().unwrap().round_start =
|
val.inner.write().local_view.as_mut().unwrap().round_start = Instant::now() -
|
||||||
Instant::now() - round_duration * num_round;
|
Duration::from_millis(
|
||||||
|
(round_duration.as_millis() as f32 * rounds_elapsed) as u64,
|
||||||
|
);
|
||||||
|
|
||||||
|
val.inner.write().peers.reshuffle();
|
||||||
|
|
||||||
let mut message_allowed = val.message_allowed();
|
let mut message_allowed = val.message_allowed();
|
||||||
|
|
||||||
move || {
|
move || {
|
||||||
@@ -2454,121 +2458,119 @@ mod tests {
|
|||||||
sum / n
|
sum / n
|
||||||
}
|
}
|
||||||
|
|
||||||
// on the first attempt we will only gossip to `sqrt(authorities)`,
|
let all_peers = authorities
|
||||||
// which should average out to 5 peers after a couple of trials
|
.iter()
|
||||||
assert_eq!(trial(test(1, &authorities)), 5);
|
.chain(full_nodes.iter())
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
// on the second (and subsequent attempts) we should gossip to all
|
// on the first attempt we will only gossip to 4 peers, either
|
||||||
// authorities we're connected to.
|
// authorities or full nodes, but we'll guarantee that half of those
|
||||||
assert_eq!(trial(test(2, &authorities)), 30);
|
// are authorities
|
||||||
assert_eq!(trial(test(3, &authorities)), 30);
|
assert!(trial(test(1.0, &authorities)) >= LUCKY_PEERS / 2);
|
||||||
|
assert_eq!(trial(test(1.0, &all_peers)), LUCKY_PEERS);
|
||||||
|
|
||||||
// we should only gossip to non-authorities after the third attempt
|
// after more than 1.5 round durations have elapsed we should gossip to
|
||||||
assert_eq!(trial(test(1, &full_nodes)), 0);
|
// `sqrt(peers)` we're connected to, but we guarantee that at least 4 of
|
||||||
assert_eq!(trial(test(2, &full_nodes)), 0);
|
// those peers are authorities (plus the `LUCKY_PEERS` from the previous
|
||||||
|
// stage)
|
||||||
// and only to `sqrt(non-authorities)`
|
assert!(trial(test(PROPAGATION_SOME * 1.1, &authorities)) >= LUCKY_PEERS);
|
||||||
assert_eq!(trial(test(3, &full_nodes)), 5);
|
assert_eq!(
|
||||||
|
trial(test(2.0, &all_peers)),
|
||||||
// only on the fourth attempt should we gossip to all non-authorities
|
LUCKY_PEERS + (all_peers.len() as f64).sqrt() as usize,
|
||||||
assert_eq!(trial(test(4, &full_nodes)), 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn only_restricts_gossip_to_authorities_after_a_minimum_threshold() {
|
|
||||||
let (val, _) = GossipValidator::<Block>::new(
|
|
||||||
config(),
|
|
||||||
voter_set_state(),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// the validator start at set id 0
|
// after 3 rounds durations we should gossip to all peers we are
|
||||||
val.note_set(SetId(0), Vec::new(), |_, _| {});
|
// connected to
|
||||||
|
assert_eq!(trial(test(PROPAGATION_ALL * 1.1, &all_peers)), all_peers.len());
|
||||||
let mut authorities = Vec::new();
|
|
||||||
for _ in 0..5 {
|
|
||||||
let peer_id = PeerId::random();
|
|
||||||
val.inner.write().peers.new_peer(peer_id.clone(), ObservedRole::Authority);
|
|
||||||
authorities.push(peer_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut message_allowed = val.message_allowed();
|
|
||||||
|
|
||||||
// since we're only connected to 5 authorities, we should never restrict
|
|
||||||
// sending of gossip messages, and instead just allow them to all
|
|
||||||
// non-authorities on the first attempt.
|
|
||||||
for authority in &authorities {
|
|
||||||
assert!(
|
|
||||||
message_allowed(
|
|
||||||
authority,
|
|
||||||
MessageIntent::Broadcast,
|
|
||||||
&crate::communication::round_topic::<Block>(1, 0),
|
|
||||||
&[],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn non_authorities_never_gossip_messages_on_first_round_duration() {
|
fn never_gossips_round_messages_to_light_clients() {
|
||||||
let mut config = config();
|
let config = config();
|
||||||
config.gossip_duration = Duration::from_secs(300); // Set to high value to prevent test race
|
|
||||||
config.local_role = Role::Full;
|
|
||||||
let round_duration = config.gossip_duration * ROUND_DURATION;
|
let round_duration = config.gossip_duration * ROUND_DURATION;
|
||||||
|
|
||||||
let (val, _) = GossipValidator::<Block>::new(config, voter_set_state(), None, None);
|
let (val, _) = GossipValidator::<Block>::new(config, voter_set_state(), None, None);
|
||||||
|
|
||||||
// the validator start at set id 0
|
// the validator starts at set id 0
|
||||||
val.note_set(SetId(0), Vec::new(), |_, _| {});
|
val.note_set(SetId(0), Vec::new(), |_, _| {});
|
||||||
|
|
||||||
let mut authorities = Vec::new();
|
// add a new light client as peer
|
||||||
for _ in 0..100 {
|
let light_peer = PeerId::random();
|
||||||
let peer_id = PeerId::random();
|
|
||||||
val.inner.write().peers.new_peer(peer_id.clone(), ObservedRole::Authority);
|
|
||||||
authorities.push(peer_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
val.inner
|
||||||
let mut message_allowed = val.message_allowed();
|
.write()
|
||||||
// since our node is not an authority we should **never** gossip any
|
.peers
|
||||||
// messages on the first attempt.
|
.new_peer(light_peer.clone(), ObservedRole::Light);
|
||||||
for authority in &authorities {
|
|
||||||
assert!(
|
|
||||||
!message_allowed(
|
|
||||||
authority,
|
|
||||||
MessageIntent::Broadcast,
|
|
||||||
&crate::communication::round_topic::<Block>(1, 0),
|
|
||||||
&[],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
assert!(!val.message_allowed()(
|
||||||
val.inner.write().local_view.as_mut().unwrap().round_start =
|
&light_peer,
|
||||||
Instant::now() - round_duration * 4;
|
MessageIntent::Broadcast,
|
||||||
let mut message_allowed = val.message_allowed();
|
&crate::communication::round_topic::<Block>(1, 0),
|
||||||
// on the fourth round duration we should allow messages to authorities
|
&[],
|
||||||
// (on the second we would do `sqrt(authorities)`)
|
));
|
||||||
for authority in &authorities {
|
|
||||||
assert!(
|
// we reverse the round start time so that the elapsed time is higher
|
||||||
message_allowed(
|
// (which should lead to more peers getting the message)
|
||||||
authority,
|
val.inner.write().local_view.as_mut().unwrap().round_start =
|
||||||
MessageIntent::Broadcast,
|
Instant::now() - round_duration * 10;
|
||||||
&crate::communication::round_topic::<Block>(1, 0),
|
|
||||||
&[],
|
// even after the round has been going for 10 round durations we will never
|
||||||
)
|
// gossip to light clients
|
||||||
);
|
assert!(!val.message_allowed()(
|
||||||
}
|
&light_peer,
|
||||||
}
|
MessageIntent::Broadcast,
|
||||||
|
&crate::communication::round_topic::<Block>(1, 0),
|
||||||
|
&[],
|
||||||
|
));
|
||||||
|
|
||||||
|
// update the peer state and local state wrt commits
|
||||||
|
val.inner
|
||||||
|
.write()
|
||||||
|
.peers
|
||||||
|
.update_peer_state(
|
||||||
|
&light_peer,
|
||||||
|
NeighborPacket {
|
||||||
|
round: Round(1),
|
||||||
|
set_id: SetId(0),
|
||||||
|
commit_finalized_height: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
val.note_commit_finalized(Round(1), SetId(0), 2, |_, _| {});
|
||||||
|
|
||||||
|
let commit = {
|
||||||
|
let commit = finality_grandpa::CompactCommit {
|
||||||
|
target_hash: H256::random(),
|
||||||
|
target_number: 2,
|
||||||
|
precommits: Vec::new(),
|
||||||
|
auth_data: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::communication::gossip::GossipMessage::<Block>::Commit(
|
||||||
|
crate::communication::gossip::FullCommitMessage {
|
||||||
|
round: Round(2),
|
||||||
|
set_id: SetId(0),
|
||||||
|
message: commit,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.encode()
|
||||||
|
};
|
||||||
|
|
||||||
|
// global messages are gossiped to light clients though
|
||||||
|
assert!(val.message_allowed()(
|
||||||
|
&light_peer,
|
||||||
|
MessageIntent::Broadcast,
|
||||||
|
&crate::communication::global_topic::<Block>(0),
|
||||||
|
&commit,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn only_gossip_commits_to_peers_on_same_set() {
|
fn only_gossip_commits_to_peers_on_same_set() {
|
||||||
let (val, _) = GossipValidator::<Block>::new(config(), voter_set_state(), None, None);
|
let (val, _) = GossipValidator::<Block>::new(config(), voter_set_state(), None, None);
|
||||||
|
|
||||||
// the validator start at set id 1
|
// the validator starts at set id 1
|
||||||
val.note_set(SetId(1), Vec::new(), |_, _| {});
|
val.note_set(SetId(1), Vec::new(), |_, _| {});
|
||||||
|
|
||||||
// add a new peer at set id 1
|
// add a new peer at set id 1
|
||||||
|
|||||||
@@ -1181,8 +1181,9 @@ where
|
|||||||
fn round_commit_timer(&self) -> Self::Timer {
|
fn round_commit_timer(&self) -> Self::Timer {
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
//random between 0-1 seconds.
|
// random between `[0, 2 * gossip_duration]` seconds.
|
||||||
let delay: u64 = thread_rng().gen_range(0, 1000);
|
let delay: u64 =
|
||||||
|
thread_rng().gen_range(0, 2 * self.config.gossip_duration.as_millis() as u64);
|
||||||
Box::pin(Delay::new(Duration::from_millis(delay)).map(Ok))
|
Box::pin(Delay::new(Duration::from_millis(delay)).map(Ok))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ use wasm_timer::Instant;
|
|||||||
// this cache should take about 256 KB of memory.
|
// this cache should take about 256 KB of memory.
|
||||||
const KNOWN_MESSAGES_CACHE_SIZE: usize = 8192;
|
const KNOWN_MESSAGES_CACHE_SIZE: usize = 8192;
|
||||||
|
|
||||||
const REBROADCAST_INTERVAL: time::Duration = time::Duration::from_secs(30);
|
const REBROADCAST_INTERVAL: time::Duration = time::Duration::from_millis(750);
|
||||||
|
|
||||||
pub(crate) const PERIODIC_MAINTENANCE_INTERVAL: time::Duration = time::Duration::from_millis(1100);
|
pub(crate) const PERIODIC_MAINTENANCE_INTERVAL: time::Duration = time::Duration::from_millis(1100);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user