Files
pezkuwi-sdk/pezkuwi/node/network/statement-distribution/src/v2/tests/grid.rs
T

2777 lines
76 KiB
Rust

// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use bitvec::order::Lsb0;
use pezkuwi_node_network_protocol::v3::{BackedCandidateAcknowledgement, BackedCandidateManifest};
use pezkuwi_node_subsystem::messages::CandidateBackingMessage;
use pezkuwi_primitives_test_helpers::make_candidate;
// Backed candidate leads to advertisement to relevant validators with relay-parent.
#[test]
fn backed_candidate_leads_to_advertisement() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_a = PeerId::random();
let peer_b = PeerId::random();
let peer_c = PeerId::random();
let peer_d = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let local_para = ParaId::from(local_group_index.0);
let other_group = next_group_index(local_group_index, validator_count, group_size);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
local_para,
test_leaf.para_data(local_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let local_group_validators = state.group_validators(local_group_index, true);
let other_group_validators = state.group_validators(other_group, true);
let v_a = local_group_validators[0];
let v_b = local_group_validators[1];
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
// peer A is in group, has relay parent in view.
// peer B is in group, has no relay parent in view.
// peer C is not in group, has relay parent in view.
// peer D is not in group, has no relay parent in view.
{
connect_peer(
&mut overseer,
peer_a.clone(),
Some(vec![state.discovery_id(v_a)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_b.clone(),
Some(vec![state.discovery_id(v_b)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
// Send gossip topology.
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
// Confirm the candidate locally so that we don't send out requests.
{
let statement = state
.sign_full_statement(
local_validator.validator_index,
Statement::Seconded(candidate.clone()),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
pvd.clone(),
)
.clone();
overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::Share(relay_parent, statement),
})
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Send enough statements to make candidate backable, make sure announcements are sent.
// Send statement from peer A.
{
let statement = state
.sign_statement(
v_a,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
send_peer_message(
&mut overseer,
peer_a.clone(),
protocol_v3::StatementDistributionMessage::Statement(relay_parent, statement),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { }
);
}
// Send statement from peer B.
{
let statement = state
.sign_statement(
v_b,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
send_peer_message(
&mut overseer,
peer_b.clone(),
protocol_v3::StatementDistributionMessage::Statement(relay_parent, statement),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_b && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { }
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
);
}
// Send Backed notification.
{
overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::Backed(candidate_hash),
})
.await;
assert_matches!(
overseer.recv().await,
AllMessages:: NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessage(
peers,
ValidationProtocols::V3(
protocol_v3::ValidationProtocol::StatementDistribution(
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest),
),
),
)
) => {
assert_eq!(peers, vec![peer_c]);
assert_eq!(manifest, BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: local_group_index,
para_id: local_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
});
}
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
overseer
});
}
#[test]
fn received_advertisement_before_confirmation_leads_to_request() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_a = PeerId::random();
let peer_b = PeerId::random();
let peer_c = PeerId::random();
let peer_d = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let other_group = next_group_index(local_group_index, validator_count, group_size);
let other_para = ParaId::from(other_group.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let local_group_validators = state.group_validators(local_group_index, true);
let other_group_validators = state.group_validators(other_group, true);
let v_a = local_group_validators[0];
let v_b = local_group_validators[1];
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
// peer A is in group, has relay parent in view.
// peer B is in group, has no relay parent in view.
// peer C is not in group, has relay parent in view.
// peer D is not in group, has relay parent in view.
{
connect_peer(
&mut overseer,
peer_a.clone(),
Some(vec![state.discovery_id(v_a)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_b.clone(),
Some(vec![state.discovery_id(v_b)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_d.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
// Send gossip topology.
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
// Receive an advertisement from C on an unconfirmed candidate.
{
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
send_peer_message(
&mut overseer,
peer_c.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest),
)
.await;
let statements = vec![
state
.sign_statement(
v_c,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
state
.sign_statement(
v_d,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
];
handle_sent_request(
&mut overseer,
peer_c,
candidate_hash,
StatementFilter::blank(group_size),
candidate.clone(),
pvd.clone(),
statements,
)
.await;
// C provided two statements we're seeing for the first time.
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into() => { }
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into() => { }
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() => { }
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
overseer
});
}
// 1. We receive manifest from grid peer, request, pass votes to backing, then receive Backed
// message. Only then should we send an acknowledgement to the grid peer.
//
// 2. (starting from end state of (1)) we receive a manifest about the same candidate from another
// grid peer and instantaneously acknowledge.
//
// Bit more context about this design choice: Statement-distribution doesn't fully emulate the
// statement logic of backing and only focuses on the number of statements. That means that we might
// request a manifest and for some reason the backing subsystem would still not consider the
// candidate as backed. So, in particular, we don't want to advertise such an unbacked candidate
// along the grid & increase load on ourselves and our peers for serving & importing such a
// candidate.
#[test]
fn received_advertisement_after_backing_leads_to_acknowledgement() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
test_harness(config, |state, mut overseer| async move {
let peers_to_connect = [
TestPeerToConnect { local: true, relay_parent_in_view: false },
TestPeerToConnect { local: true, relay_parent_in_view: false },
TestPeerToConnect { local: false, relay_parent_in_view: true },
TestPeerToConnect { local: false, relay_parent_in_view: true },
TestPeerToConnect { local: false, relay_parent_in_view: true },
];
let TestSetupInfo {
other_group,
other_para,
relay_parent,
test_leaf,
peers,
validators,
..
} = setup_test_and_connect_peers(
&state,
&mut overseer,
validator_count,
group_size,
&peers_to_connect,
false,
)
.await;
let [_, _, peer_c, peer_d, _] = peers[..] else { panic!() };
let [_, _, v_c, v_d, v_e] = validators[..] else { panic!() };
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
let statement_c = state
.sign_statement(
v_c,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
let statement_d = state
.sign_statement(
v_d,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
// Receive an advertisement from C.
{
send_manifest_from_peer(&mut overseer, peer_c, manifest.clone()).await;
// Should send a request to C.
let statements = vec![
statement_c.clone(),
statement_d.clone(),
state
.sign_statement(
v_e,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
];
handle_sent_request(
&mut overseer,
peer_c,
candidate_hash,
StatementFilter::blank(group_size),
candidate.clone(),
pvd.clone(),
statements,
)
.await;
assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_RESPONSE);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Receive Backed message.
send_backed_message(&mut overseer, candidate_hash).await;
// Should send an acknowledgement back to C.
{
assert_matches!(
overseer.recv().await,
AllMessages:: NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessage(
peers,
ValidationProtocols::V3(
protocol_v3::ValidationProtocol::StatementDistribution(
protocol_v3::StatementDistributionMessage::BackedCandidateKnown(ack),
),
),
)
) => {
assert_eq!(peers, vec![peer_c]);
assert_eq!(ack, BackedCandidateAcknowledgement {
candidate_hash,
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
});
}
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Receive a manifest about the same candidate from peer D.
{
send_manifest_from_peer(&mut overseer, peer_d, manifest.clone()).await;
let expected_ack = BackedCandidateAcknowledgement {
candidate_hash,
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
// Instantaneously acknowledge.
assert_matches!(
overseer.recv().await,
AllMessages:: NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessages(messages)
) => {
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].0, vec![peer_d]);
assert_matches!(
&messages[0].1,
ValidationProtocols::V3(protocol_v3::ValidationProtocol::StatementDistribution(
protocol_v3::StatementDistributionMessage::BackedCandidateKnown(ack)
)) if *ack == expected_ack
);
}
);
}
overseer
});
}
#[test]
fn receive_ack_for_unconfirmed_candidate() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
test_harness(config, |state, mut overseer| async move {
let peers_to_connect = [
TestPeerToConnect { local: true, relay_parent_in_view: true },
TestPeerToConnect { local: true, relay_parent_in_view: false },
TestPeerToConnect { local: false, relay_parent_in_view: true },
TestPeerToConnect { local: false, relay_parent_in_view: false },
];
let TestSetupInfo { local_para, relay_parent, test_leaf, peers, .. } =
setup_test_and_connect_peers(
&state,
&mut overseer,
validator_count,
group_size,
&peers_to_connect,
false,
)
.await;
let [_, _, peer_c, _] = peers[..] else { panic!() };
let (candidate, _pvd) = make_candidate(
relay_parent,
1,
local_para,
test_leaf.para_data(local_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let ack = BackedCandidateAcknowledgement {
candidate_hash,
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
// Receive an acknowledgement from a peer before the candidate is confirmed.
send_ack_from_peer(&mut overseer, peer_c, ack.clone()).await;
assert_peer_reported!(
&mut overseer,
peer_c,
COST_UNEXPECTED_ACKNOWLEDGEMENT_UNKNOWN_CANDIDATE,
);
overseer
});
}
// Test receiving unexpected and expected acknowledgements for a locally confirmed candidate.
#[test]
fn received_acknowledgements_for_locally_confirmed() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
test_harness(config, |state, mut overseer| async move {
let peers_to_connect = [
TestPeerToConnect { local: true, relay_parent_in_view: true },
TestPeerToConnect { local: true, relay_parent_in_view: false },
TestPeerToConnect { local: false, relay_parent_in_view: true },
TestPeerToConnect { local: false, relay_parent_in_view: false },
];
let TestSetupInfo {
local_validator,
local_group,
local_para,
relay_parent,
test_leaf,
peers,
validators,
..
} = setup_test_and_connect_peers(
&state,
&mut overseer,
validator_count,
group_size,
&peers_to_connect,
true,
)
.await;
let [peer_a, peer_b, peer_c, peer_d] = peers[..] else { panic!() };
let [_, v_b, _, _] = validators[..] else { panic!() };
let (candidate, pvd) = make_candidate(
relay_parent,
1,
local_para,
test_leaf.para_data(local_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let ack = BackedCandidateAcknowledgement {
candidate_hash,
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
// Confirm the candidate locally so that we don't send out requests.
{
let statement = state
.sign_full_statement(
local_validator.validator_index,
Statement::Seconded(candidate.clone()),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
pvd.clone(),
)
.clone();
send_share_message(&mut overseer, relay_parent, statement).await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Receive an unexpected acknowledgement from peer D.
send_ack_from_peer(&mut overseer, peer_d, ack.clone()).await;
assert_peer_reported!(&mut overseer, peer_d, COST_UNEXPECTED_MANIFEST_DISALLOWED);
// Send statement from peer B.
{
let statement = state
.sign_statement(
v_b,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
send_peer_message(
&mut overseer,
peer_b.clone(),
protocol_v3::StatementDistributionMessage::Statement(relay_parent, statement),
)
.await;
assert_peer_reported!(&mut overseer, peer_b, BENEFIT_VALID_STATEMENT_FIRST);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
);
}
// Send Backed notification.
{
send_backed_message(&mut overseer, candidate_hash).await;
// We should send out a manifest.
assert_matches!(
overseer.recv().await,
AllMessages:: NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessage(
peers,
ValidationProtocols::V3(
protocol_v3::ValidationProtocol::StatementDistribution(
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest),
),
),
)
) => {
assert_eq!(peers, vec![peer_c]);
assert_eq!(manifest, BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: local_group,
para_id: local_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
});
}
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Receive an unexpected acknowledgement from peer D.
//
// It still shouldn't know this manifest.
send_ack_from_peer(&mut overseer, peer_d, ack.clone()).await;
assert_peer_reported!(&mut overseer, peer_d, COST_UNEXPECTED_MANIFEST_DISALLOWED);
// Receive an acknowledgement from peer C.
//
// It's OK, we know they know it because we sent them a manifest.
send_ack_from_peer(&mut overseer, peer_c, ack.clone()).await;
// What happens if we get another valid ack?
send_ack_from_peer(&mut overseer, peer_c, ack.clone()).await;
overseer
});
}
// Test receiving unexpected acknowledgements for a candidate confirmed in a different group.
#[test]
fn received_acknowledgements_for_externally_confirmed() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
test_harness(config, |state, mut overseer| async move {
let peers_to_connect = [
TestPeerToConnect { local: true, relay_parent_in_view: true },
TestPeerToConnect { local: true, relay_parent_in_view: false },
TestPeerToConnect { local: false, relay_parent_in_view: true },
TestPeerToConnect { local: false, relay_parent_in_view: true },
TestPeerToConnect { local: false, relay_parent_in_view: true },
];
let TestSetupInfo {
other_group,
other_para,
relay_parent,
test_leaf,
peers,
validators,
..
} = setup_test_and_connect_peers(
&state,
&mut overseer,
validator_count,
group_size,
&peers_to_connect,
false,
)
.await;
let [peer_a, _, peer_c, peer_d, _] = peers[..] else { panic!() };
let [_, _, v_c, v_d, v_e] = validators[..] else { panic!() };
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
let statement_c = state
.sign_statement(
v_c,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
let statement_d = state
.sign_statement(
v_d,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
// Receive an advertisement from C, confirming the candidate.
{
send_manifest_from_peer(&mut overseer, peer_c, manifest.clone()).await;
// Should send a request to C.
let statements = vec![
statement_c.clone(),
statement_d.clone(),
state
.sign_statement(
v_e,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
];
handle_sent_request(
&mut overseer,
peer_c,
candidate_hash,
StatementFilter::blank(group_size),
candidate.clone(),
pvd.clone(),
statements,
)
.await;
assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_RESPONSE);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
let ack = BackedCandidateAcknowledgement {
candidate_hash,
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
// Receive an unexpected acknowledgement from peer D.
send_ack_from_peer(&mut overseer, peer_d, ack.clone()).await;
assert_peer_reported!(&mut overseer, peer_d, COST_UNEXPECTED_MANIFEST_PEER_UNKNOWN);
// Receive an unexpected acknowledgement from peer A.
send_ack_from_peer(&mut overseer, peer_a, ack.clone()).await;
assert_peer_reported!(&mut overseer, peer_a, COST_UNEXPECTED_MANIFEST_DISALLOWED);
overseer
});
}
// Received advertisement after confirmation but before backing leads to nothing.
#[test]
fn received_advertisement_after_confirmation_before_backing() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_c = PeerId::random();
let peer_d = PeerId::random();
let peer_e = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let other_group = next_group_index(local_group_index, validator_count, group_size);
let other_para = ParaId::from(other_group.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let other_group_validators = state.group_validators(other_group, true);
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
let v_e = other_group_validators[2];
// Connect C, D, E
{
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_e.clone(),
Some(vec![state.discovery_id(v_e)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_d.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
// Send gossip topology.
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
let statement_c = state
.sign_statement(
v_c,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
let statement_d = state
.sign_statement(
v_d,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
// Receive an advertisement from C.
{
send_peer_message(
&mut overseer,
peer_c.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
// Should send a request to C.
let statements = vec![
statement_c.clone(),
statement_d.clone(),
state
.sign_statement(
v_e,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
];
handle_sent_request(
&mut overseer,
peer_c,
candidate_hash,
StatementFilter::blank(group_size),
candidate.clone(),
pvd.clone(),
statements,
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_RESPONSE.into()
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Receive advertisement from peer D (after confirmation but before backing).
{
send_peer_message(
&mut overseer,
peer_d.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
}
overseer
});
}
#[test]
fn additional_statements_are_shared_after_manifest_exchange() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_c = PeerId::random();
let peer_d = PeerId::random();
let peer_e = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let other_group = next_group_index(local_group_index, validator_count, group_size);
let other_para = ParaId::from(other_group.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let other_group_validators = state.group_validators(other_group, true);
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
let v_e = other_group_validators[2];
// Connect C, D, E
{
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_e.clone(),
Some(vec![state.discovery_id(v_e)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_d.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
// Send gossip topology.
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
// Receive an advertisement from C.
{
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
send_peer_message(
&mut overseer,
peer_c.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
}
// Should send a request to C.
{
let statements = vec![
state
.sign_statement(
v_d,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
state
.sign_statement(
v_e,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
];
handle_sent_request(
&mut overseer,
peer_c,
candidate_hash,
StatementFilter::blank(group_size),
candidate.clone(),
pvd.clone(),
statements,
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_RESPONSE.into()
);
}
let hypothetical = HypotheticalCandidate::Complete {
candidate_hash,
receipt: Arc::new(candidate.clone()),
persisted_validation_data: pvd.clone(),
};
let membership = vec![relay_parent];
answer_expected_hypothetical_membership_request(
&mut overseer,
vec![(hypothetical, membership)],
)
.await;
// Statements are sent to the Backing subsystem.
{
assert_matches!(
overseer.recv().await,
AllMessages::CandidateBacking(
CandidateBackingMessage::Statement(hash, statement)
) => {
assert_eq!(hash, relay_parent);
assert_matches!(
statement.payload(),
FullStatementWithPVD::Seconded(c, p)
if c == &candidate && p == &pvd
);
}
);
assert_matches!(
overseer.recv().await,
AllMessages::CandidateBacking(
CandidateBackingMessage::Statement(hash, statement)
) => {
assert_eq!(hash, relay_parent);
assert_matches!(
statement.payload(),
FullStatementWithPVD::Seconded(c, p)
if c == &candidate && p == &pvd
);
}
);
}
// Receive Backed message.
overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::Backed(candidate_hash),
})
.await;
// Should send an acknowledgement back to C.
{
assert_matches!(
overseer.recv().await,
AllMessages:: NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessage(
peers,
ValidationProtocols::V3(
protocol_v3::ValidationProtocol::StatementDistribution(
protocol_v3::StatementDistributionMessage::BackedCandidateKnown(ack),
),
),
)
) => {
assert_eq!(peers, vec![peer_c]);
assert_eq!(ack, BackedCandidateAcknowledgement {
candidate_hash,
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
});
}
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Receive a manifest about the same candidate from peer D. Contains different statements.
{
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 0],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
send_peer_message(
&mut overseer,
peer_d.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
let expected_ack = BackedCandidateAcknowledgement {
candidate_hash,
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
// Instantaneously acknowledge.
assert_matches!(
overseer.recv().await,
AllMessages:: NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessages(messages)
) => {
assert_eq!(messages.len(), 2);
assert_eq!(messages[0].0, vec![peer_d]);
assert_eq!(messages[1].0, vec![peer_d]);
assert_matches!(
&messages[0].1,
ValidationProtocols::V3(protocol_v3::ValidationProtocol::StatementDistribution(
protocol_v3::StatementDistributionMessage::BackedCandidateKnown(ack)
)) if *ack == expected_ack
);
assert_matches!(
&messages[1].1,
ValidationProtocols::V3(protocol_v3::ValidationProtocol::StatementDistribution(
protocol_v3::StatementDistributionMessage::Statement(r, s)
)) if *r == relay_parent && s.unchecked_payload() == &CompactStatement::Seconded(candidate_hash) && s.unchecked_validator_index() == v_e
);
}
);
}
overseer
});
}
// Grid-sending validator view entering relay-parent leads to advertisement.
#[test]
fn advertisement_sent_when_peer_enters_relay_parent_view() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_a = PeerId::random();
let peer_b = PeerId::random();
let peer_c = PeerId::random();
let peer_d = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let local_para = ParaId::from(local_group_index.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
local_para,
test_leaf.para_data(local_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let local_group_validators = state.group_validators(local_group_index, true);
let other_group_validators = state.group_validators((local_group_index.0 + 1).into(), true);
let v_a = local_group_validators[0];
let v_b = local_group_validators[1];
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
// peer A is in group, has relay parent in view.
// peer B is in group, has no relay parent in view.
// peer C is not in group, has relay parent in view.
// peer D is not in group, has no relay parent in view.
{
connect_peer(
&mut overseer,
peer_a.clone(),
Some(vec![state.discovery_id(v_a)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_b.clone(),
Some(vec![state.discovery_id(v_b)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
// Confirm the candidate locally so that we don't send out requests.
{
let statement = state
.sign_full_statement(
local_validator.validator_index,
Statement::Seconded(candidate.clone()),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
pvd.clone(),
)
.clone();
overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::Share(relay_parent, statement),
})
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Send enough statements to make candidate backable, make sure announcements are sent.
// Send statement from peer A.
{
let statement = state
.sign_statement(
v_a,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
send_peer_message(
&mut overseer,
peer_a.clone(),
protocol_v3::StatementDistributionMessage::Statement(relay_parent, statement),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { }
);
}
// Send statement from peer B.
{
let statement = state
.sign_statement(
v_b,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
send_peer_message(
&mut overseer,
peer_b.clone(),
protocol_v3::StatementDistributionMessage::Statement(relay_parent, statement),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_b && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { }
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
);
}
// Send Backed notification.
overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::Backed(candidate_hash),
})
.await;
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
// Relay parent enters view of peer C.
{
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
let expected_manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: local_group_index,
para_id: local_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
assert_matches!(
overseer.recv().await,
AllMessages:: NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessages(messages)
) => {
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].0, vec![peer_c]);
assert_matches!(
&messages[0].1,
ValidationProtocols::V3(protocol_v3::ValidationProtocol::StatementDistribution(
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest)
)) => {
assert_eq!(*manifest, expected_manifest);
}
);
}
);
}
overseer
});
}
// Advertisement not re-sent after re-entering relay parent (view oscillation).
#[test]
fn advertisement_not_re_sent_when_peer_re_enters_view() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_a = PeerId::random();
let peer_b = PeerId::random();
let peer_c = PeerId::random();
let peer_d = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let local_para = ParaId::from(local_group_index.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
local_para,
test_leaf.para_data(local_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let local_group_validators = state.group_validators(local_group_index, true);
let other_group_validators = state.group_validators((local_group_index.0 + 1).into(), true);
let v_a = local_group_validators[0];
let v_b = local_group_validators[1];
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
// peer A is in group, has relay parent in view.
// peer B is in group, has no relay parent in view.
// peer C is not in group, has relay parent in view.
// peer D is not in group, has no relay parent in view.
{
connect_peer(
&mut overseer,
peer_a.clone(),
Some(vec![state.discovery_id(v_a)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_b.clone(),
Some(vec![state.discovery_id(v_b)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
// Confirm the candidate locally so that we don't send out requests.
{
let statement = state
.sign_full_statement(
local_validator.validator_index,
Statement::Seconded(candidate.clone()),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
pvd.clone(),
)
.clone();
overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::Share(relay_parent, statement),
})
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Send enough statements to make candidate backable, make sure announcements are sent.
// Send statement from peer A.
{
let statement = state
.sign_statement(
v_a,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
send_peer_message(
&mut overseer,
peer_a.clone(),
protocol_v3::StatementDistributionMessage::Statement(relay_parent, statement),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { }
);
}
// Send statement from peer B.
{
let statement = state
.sign_statement(
v_b,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
send_peer_message(
&mut overseer,
peer_b.clone(),
protocol_v3::StatementDistributionMessage::Statement(relay_parent, statement),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_b && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { }
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
);
}
// Send Backed notification.
{
overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::Backed(candidate_hash),
})
.await;
assert_matches!(
overseer.recv().await,
AllMessages:: NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessage(
peers,
ValidationProtocols::V3(
protocol_v3::ValidationProtocol::StatementDistribution(
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest),
),
),
)
) => {
assert_eq!(peers, vec![peer_c]);
assert_eq!(manifest, BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: local_group_index,
para_id: local_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
});
}
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Peer leaves view.
send_peer_view_change(&mut overseer, peer_c.clone(), view![]).await;
// Peer re-enters view.
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
overseer
});
}
fn inner_grid_statements_imported_to_backing(groups_for_first_para: usize) {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_c = PeerId::random();
let peer_d = PeerId::random();
let peer_e = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let other_group = next_group_index(local_group_index, validator_count, group_size);
// Other para is same para for elastic scaling test (groups_for_first_para > 1)
let other_para = ParaId::from((groups_for_first_para == 1) as u32);
let test_leaf =
state.make_dummy_leaf_with_multiple_cores_per_para(relay_parent, groups_for_first_para);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let other_group_validators = state.group_validators(other_group, true);
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
let v_e = other_group_validators[2];
// Connect C, D, E
{
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_e.clone(),
Some(vec![state.discovery_id(v_e)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_d.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
// Receive an advertisement from C.
{
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
send_peer_message(
&mut overseer,
peer_c.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
}
// Should send a request to C.
{
let statements = vec![
state
.sign_statement(
v_d,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
state
.sign_statement(
v_e,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
];
handle_sent_request(
&mut overseer,
peer_c,
candidate_hash,
StatementFilter::blank(group_size),
candidate.clone(),
pvd.clone(),
statements,
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_RESPONSE.into()
);
}
let hypothetical = HypotheticalCandidate::Complete {
candidate_hash,
receipt: Arc::new(candidate.clone()),
persisted_validation_data: pvd.clone(),
};
let membership = vec![relay_parent];
answer_expected_hypothetical_membership_request(
&mut overseer,
vec![(hypothetical, membership)],
)
.await;
// Receive messages from Backing subsystem.
{
assert_matches!(
overseer.recv().await,
AllMessages::CandidateBacking(
CandidateBackingMessage::Statement(hash, statement)
) => {
assert_eq!(hash, relay_parent);
assert_matches!(
statement.payload(),
FullStatementWithPVD::Seconded(c, p)
if c == &candidate && p == &pvd
);
}
);
assert_matches!(
overseer.recv().await,
AllMessages::CandidateBacking(
CandidateBackingMessage::Statement(hash, statement)
) => {
assert_eq!(hash, relay_parent);
assert_matches!(
statement.payload(),
FullStatementWithPVD::Seconded(c, p)
if c == &candidate && p == &pvd
);
}
);
}
overseer
});
}
// Grid statements imported to backing once candidate enters hypothetical frontier.
#[test]
fn grid_statements_imported_to_backing() {
inner_grid_statements_imported_to_backing(1);
}
// Grid statements imported to backing once candidate enters hypothetical frontier.
// All statements are for candidates of the same teyrchain but from different backing groups.
#[test]
fn elastic_scaling_grid_statements_imported_to_backing() {
inner_grid_statements_imported_to_backing(2);
}
#[test]
fn advertisements_rejected_from_incorrect_peers() {
sp_tracing::try_init_simple();
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_a = PeerId::random();
let peer_b = PeerId::random();
let peer_c = PeerId::random();
let peer_d = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let other_group = next_group_index(local_group_index, validator_count, group_size);
let other_para = ParaId::from(other_group.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let local_group_validators = state.group_validators(local_group_index, true);
let other_group_validators = state.group_validators(other_group, true);
let v_a = local_group_validators[0];
let v_b = local_group_validators[1];
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
// peer A is in group, has relay parent in view.
// peer B is in group, has no relay parent in view.
// peer C is not in group, has relay parent in view.
// peer D is not in group, has no relay parent in view.
{
connect_peer(
&mut overseer,
peer_a.clone(),
Some(vec![state.discovery_id(v_a)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_b.clone(),
Some(vec![state.discovery_id(v_b)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
// Receive an advertisement from A (our group).
{
send_peer_message(
&mut overseer,
peer_a.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
// Message not expected from peers of our own group.
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_a && r == COST_UNEXPECTED_MANIFEST_PEER_UNKNOWN.into() => { }
);
}
// Receive an advertisement from B (our group).
{
send_peer_message(
&mut overseer,
peer_b.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest),
)
.await;
// Message not expected from peers of our own group.
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_b && r == COST_UNEXPECTED_MANIFEST_PEER_UNKNOWN.into() => { }
);
}
overseer
});
}
#[test]
fn manifest_rejected_with_unknown_relay_parent() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let unknown_parent = Hash::repeat_byte(2);
let peer_c = PeerId::random();
let peer_d = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let other_group = next_group_index(local_group_index, validator_count, group_size);
let other_para = ParaId::from(other_group.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
unknown_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let other_group_validators = state.group_validators(other_group, true);
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
// peer C is not in group, has relay parent in view.
// peer D is not in group, has no relay parent in view.
{
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
let manifest = BackedCandidateManifest {
relay_parent: unknown_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
// Receive an advertisement from C.
{
send_peer_message(
&mut overseer,
peer_c.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == COST_UNEXPECTED_MANIFEST_MISSING_KNOWLEDGE.into() => { }
);
}
overseer
});
}
#[test]
fn manifest_rejected_when_not_a_validator() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::None,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_c = PeerId::random();
let peer_d = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let other_group = GroupIndex::from(0);
let other_para = ParaId::from(other_group.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let other_group_validators = state.group_validators(other_group, true);
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
// peer C is not in group, has relay parent in view.
// peer D is not in group, has no relay parent in view.
{
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
// Receive an advertisement from C.
{
send_peer_message(
&mut overseer,
peer_c.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == COST_UNEXPECTED_MANIFEST_MISSING_KNOWLEDGE.into() => { }
);
}
overseer
});
}
#[test]
fn manifest_rejected_when_group_does_not_match_para() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_c = PeerId::random();
let peer_d = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let other_group = next_group_index(local_group_index, validator_count, group_size);
// Create a mismatch between group and para.
let other_para = next_group_index(other_group, validator_count, group_size);
let other_para = ParaId::from(other_para.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let other_group_validators = state.group_validators(other_group, true);
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
// peer C is not in group, has relay parent in view.
// peer D is not in group, has no relay parent in view.
{
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
},
};
// Receive an advertisement from C.
{
send_peer_message(
&mut overseer,
peer_c.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == COST_MALFORMED_MANIFEST.into() => { }
);
}
overseer
});
}
#[test]
fn peer_reported_for_advertisement_conflicting_with_confirmed_candidate() {
let validator_count = 6;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::Validator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_c = PeerId::random();
let peer_d = PeerId::random();
let peer_e = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
let local_group_index = local_validator.group_index.unwrap();
let other_group = next_group_index(local_group_index, validator_count, group_size);
let other_para = ParaId::from(other_group.0);
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
other_para,
test_leaf.para_data(other_para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let other_group_validators = state.group_validators(other_group, true);
let v_c = other_group_validators[0];
let v_d = other_group_validators[1];
let v_e = other_group_validators[2];
// Connect C, D, E
{
connect_peer(
&mut overseer,
peer_c.clone(),
Some(vec![state.discovery_id(v_c)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_d.clone(),
Some(vec![state.discovery_id(v_d)].into_iter().collect()),
)
.await;
connect_peer(
&mut overseer,
peer_e.clone(),
Some(vec![state.discovery_id(v_e)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_d.clone(), view![relay_parent]).await;
send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await;
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: other_group,
para_id: other_para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 0],
},
};
let statement_c = state
.sign_statement(
v_c,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
let statement_d = state
.sign_statement(
v_d,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone();
// Receive an advertisement from C.
{
send_peer_message(
&mut overseer,
peer_c.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest.clone(),
),
)
.await;
// Should send a request to C.
let statements = vec![
statement_c.clone(),
statement_d.clone(),
state
.sign_statement(
v_e,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
];
handle_sent_request(
&mut overseer,
peer_c,
candidate_hash,
StatementFilter::blank(group_size),
candidate.clone(),
pvd.clone(),
statements,
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
);
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == BENEFIT_VALID_RESPONSE.into()
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
}
// Receive conflicting advertisement from peer C after confirmation.
//
// NOTE: This causes a conflict because we track received manifests on a per-validator
// basis, and this is the second time we're getting a manifest from C.
{
let mut manifest = manifest.clone();
manifest.statement_knowledge = StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 0],
validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
};
send_peer_message(
&mut overseer,
peer_c.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest),
)
.await;
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_c && r == COST_CONFLICTING_MANIFEST.into()
);
}
overseer
});
}
#[test]
fn inactive_local_participates_in_grid() {
let validator_count = 11;
let group_size = 3;
let config = TestConfig {
validator_count,
group_size,
local_validator: LocalRole::InactiveValidator,
allow_v2_descriptors: false,
};
let relay_parent = Hash::repeat_byte(1);
let peer_a = PeerId::random();
test_harness(config, |state, mut overseer| async move {
let local_validator = state.local.clone().unwrap();
assert_eq!(local_validator.validator_index.0, validator_count as u32);
let group_idx = GroupIndex::from(0);
let para = ParaId::from(0);
// Dummy leaf is needed to update topology.
let dummy_leaf = state.make_dummy_leaf(Hash::repeat_byte(2));
let test_leaf = state.make_dummy_leaf(relay_parent);
let (candidate, pvd) = make_candidate(
relay_parent,
1,
para,
test_leaf.para_data(para).head_data.clone(),
vec![4, 5, 6].into(),
Hash::repeat_byte(42).into(),
);
let candidate_hash = candidate.hash();
let first_group = state.group_validators(group_idx, true);
let v_a = first_group.last().unwrap().clone();
let v_b = first_group.first().unwrap().clone();
{
connect_peer(
&mut overseer,
peer_a.clone(),
Some(vec![state.discovery_id(v_a)].into_iter().collect()),
)
.await;
send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await;
}
activate_leaf(&mut overseer, &dummy_leaf, &state, true, vec![]).await;
// Send gossip topology.
send_new_topology(&mut overseer, state.make_dummy_topology()).await;
activate_leaf(&mut overseer, &test_leaf, &state, false, vec![]).await;
// Receive an advertisement from A.
let manifest = BackedCandidateManifest {
relay_parent,
candidate_hash,
group_index: group_idx,
para_id: para,
parent_head_data_hash: pvd.parent_head.hash(),
statement_knowledge: StatementFilter {
seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 0, 1],
validated_in_group: bitvec::bitvec![u8, Lsb0; 1, 0, 1],
},
};
send_peer_message(
&mut overseer,
peer_a.clone(),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest),
)
.await;
let statements = vec![
state
.sign_statement(
v_a,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
state
.sign_statement(
v_b,
CompactStatement::Seconded(candidate_hash),
&SigningContext { parent_hash: relay_parent, session_index: 1 },
)
.as_unchecked()
.clone(),
];
// Inactive node requests this candidate.
handle_sent_request(
&mut overseer,
peer_a,
candidate_hash,
StatementFilter::blank(group_size),
candidate.clone(),
pvd.clone(),
statements,
)
.await;
for _ in 0..2 {
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_a && r == BENEFIT_VALID_STATEMENT.into() => { }
);
}
assert_matches!(
overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
if p == peer_a && r == BENEFIT_VALID_RESPONSE.into() => { }
);
answer_expected_hypothetical_membership_request(&mut overseer, vec![]).await;
overseer
});
}