mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 09:37:55 +00:00
Delay reputation updates (#7214)
* Add futures-timer * Make cost_or_benefit public * Update ReportPeer message format * Add delay to reputation updates (dirtywork) * Update ReputationAggregator * Update tests * Fix flucky tests * Move reputation to state * Use the main loop for handling reputation sendings * Update * Move reputation to utils * Update reputation sending * Fix arguments order * Update state * Remove new from state * Add constant * Add failing test for delay * Change mocking approach * Fix type errors * Fix comments * Add message handling to select * Fix bitfields-distribution tests * Add docs to reputation aggregator * Replace .into_base_rep * Use one REPUTATION_CHANGE_INTERVAL by default * Add reputation change to statement-distribution * Update polkadot-availability-bitfield-distribution * Update futures selecting in subsystems * Update reputation adding * Send malicious changes right away without adding to state * Add reputation to StatementDistributionSubsystem * Handle reputation in statement distribution * Add delay test for polkadot-statement-distribution * Fix collator-protocol tests before applying reputation delay * Remove into_base_rep * Add reputation to State * Fix failed tests * Add reputation delay * Update tests * Add batched network message for peer reporting * Update approval-distribution tests * Update bitfield-distribution tests * Update statement-distribution tests * Update collator-protocol tests * Remove levels in matching * Address clippy errors * Fix overseer test * Add a metric for original count of rep changes * Update Reputation * Revert "Add a metric for original count of rep changes" This reverts commit 6c9b0c1ec34491d16e562bdcba8db6b9dcf484db. * Update node/subsystem-util/src/reputation.rs Co-authored-by: Vsevolod Stakhov <vsevolod.stakhov@parity.io> * Remove redundant vec --------- Co-authored-by: Vsevolod Stakhov <vsevolod.stakhov@parity.io>
This commit is contained in:
@@ -55,12 +55,19 @@ use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future::RemoteHandle,
|
||||
prelude::*,
|
||||
select,
|
||||
};
|
||||
use indexmap::{map::Entry as IEntry, IndexMap};
|
||||
use sp_keystore::KeystorePtr;
|
||||
use util::runtime::RuntimeInfo;
|
||||
use util::{
|
||||
reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL},
|
||||
runtime::RuntimeInfo,
|
||||
};
|
||||
|
||||
use std::collections::{hash_map::Entry, HashMap, HashSet, VecDeque};
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap, HashSet, VecDeque},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use fatality::Nested;
|
||||
|
||||
@@ -126,6 +133,8 @@ pub struct StatementDistributionSubsystem<R> {
|
||||
metrics: Metrics,
|
||||
/// Pseudo-random generator for peers selection logic
|
||||
rng: R,
|
||||
/// Aggregated reputation change
|
||||
reputation: ReputationAggregator,
|
||||
}
|
||||
|
||||
#[overseer::subsystem(StatementDistribution, error=SubsystemError, prefix=self::overseer)]
|
||||
@@ -1167,12 +1176,14 @@ async fn send_statements<Context>(
|
||||
}
|
||||
}
|
||||
|
||||
async fn report_peer(
|
||||
/// Modify the reputation of a peer based on its behavior.
|
||||
async fn modify_reputation(
|
||||
reputation: &mut ReputationAggregator,
|
||||
sender: &mut impl overseer::StatementDistributionSenderTrait,
|
||||
peer: PeerId,
|
||||
rep: Rep,
|
||||
) {
|
||||
sender.send_message(NetworkBridgeTxMessage::ReportPeer(peer, rep)).await
|
||||
reputation.modify(sender, peer, rep).await;
|
||||
}
|
||||
|
||||
/// If message contains a statement, then retrieve it, otherwise fork task to fetch it.
|
||||
@@ -1319,6 +1330,7 @@ async fn handle_incoming_message_and_circulate<'a, Context, R>(
|
||||
metrics: &Metrics,
|
||||
runtime: &mut RuntimeInfo,
|
||||
rng: &mut R,
|
||||
reputation: &mut ReputationAggregator,
|
||||
) where
|
||||
R: rand::Rng,
|
||||
{
|
||||
@@ -1333,6 +1345,7 @@ async fn handle_incoming_message_and_circulate<'a, Context, R>(
|
||||
message,
|
||||
req_sender,
|
||||
metrics,
|
||||
reputation,
|
||||
)
|
||||
.await,
|
||||
None => None,
|
||||
@@ -1397,6 +1410,7 @@ async fn handle_incoming_message<'a, Context>(
|
||||
message: protocol_v1::StatementDistributionMessage,
|
||||
req_sender: &mpsc::Sender<RequesterMessage>,
|
||||
metrics: &Metrics,
|
||||
reputation: &mut ReputationAggregator,
|
||||
) -> Option<(Hash, StoredStatement<'a>)> {
|
||||
let relay_parent = message.get_relay_parent();
|
||||
let _ = metrics.time_network_bridge_update_v1("handle_incoming_message");
|
||||
@@ -1411,7 +1425,7 @@ async fn handle_incoming_message<'a, Context>(
|
||||
);
|
||||
|
||||
if !recent_outdated_heads.is_recent_outdated(&relay_parent) {
|
||||
report_peer(ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await;
|
||||
modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await;
|
||||
}
|
||||
|
||||
return None
|
||||
@@ -1421,7 +1435,7 @@ async fn handle_incoming_message<'a, Context>(
|
||||
if let protocol_v1::StatementDistributionMessage::LargeStatement(_) = message {
|
||||
if let Err(rep) = peer_data.receive_large_statement(&relay_parent) {
|
||||
gum::debug!(target: LOG_TARGET, ?peer, ?message, ?rep, "Unexpected large statement.",);
|
||||
report_peer(ctx.sender(), peer, rep).await;
|
||||
modify_reputation(reputation, ctx.sender(), peer, rep).await;
|
||||
return None
|
||||
}
|
||||
}
|
||||
@@ -1462,16 +1476,16 @@ async fn handle_incoming_message<'a, Context>(
|
||||
// Report peer merely if this is not a duplicate out-of-view statement that
|
||||
// was caused by a missing Seconded statement from this peer
|
||||
if unexpected_count == 0_usize {
|
||||
report_peer(ctx.sender(), peer, rep).await;
|
||||
modify_reputation(reputation, ctx.sender(), peer, rep).await;
|
||||
}
|
||||
},
|
||||
// This happens when we have an unexpected remote peer that announced Seconded
|
||||
COST_UNEXPECTED_STATEMENT_REMOTE => {
|
||||
metrics.on_unexpected_statement_seconded();
|
||||
report_peer(ctx.sender(), peer, rep).await;
|
||||
modify_reputation(reputation, ctx.sender(), peer, rep).await;
|
||||
},
|
||||
_ => {
|
||||
report_peer(ctx.sender(), peer, rep).await;
|
||||
modify_reputation(reputation, ctx.sender(), peer, rep).await;
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1492,7 +1506,7 @@ async fn handle_incoming_message<'a, Context>(
|
||||
peer_data
|
||||
.receive(&relay_parent, &fingerprint, max_message_count)
|
||||
.expect("checked in `check_can_receive` above; qed");
|
||||
report_peer(ctx.sender(), peer, BENEFIT_VALID_STATEMENT).await;
|
||||
modify_reputation(reputation, ctx.sender(), peer, BENEFIT_VALID_STATEMENT).await;
|
||||
|
||||
return None
|
||||
},
|
||||
@@ -1502,7 +1516,7 @@ async fn handle_incoming_message<'a, Context>(
|
||||
match check_statement_signature(&active_head, relay_parent, unchecked_compact) {
|
||||
Err(statement) => {
|
||||
gum::debug!(target: LOG_TARGET, ?peer, ?statement, "Invalid statement signature");
|
||||
report_peer(ctx.sender(), peer, COST_INVALID_SIGNATURE).await;
|
||||
modify_reputation(reputation, ctx.sender(), peer, COST_INVALID_SIGNATURE).await;
|
||||
return None
|
||||
},
|
||||
Ok(statement) => statement,
|
||||
@@ -1528,7 +1542,7 @@ async fn handle_incoming_message<'a, Context>(
|
||||
is_large_statement,
|
||||
"Full statement had bad payload."
|
||||
);
|
||||
report_peer(ctx.sender(), peer, COST_WRONG_HASH).await;
|
||||
modify_reputation(reputation, ctx.sender(), peer, COST_WRONG_HASH).await;
|
||||
return None
|
||||
},
|
||||
Ok(statement) => statement,
|
||||
@@ -1567,7 +1581,7 @@ async fn handle_incoming_message<'a, Context>(
|
||||
unreachable!("checked in `is_useful_or_unknown` above; qed");
|
||||
},
|
||||
NotedStatement::Fresh(statement) => {
|
||||
report_peer(ctx.sender(), peer, BENEFIT_VALID_STATEMENT_FIRST).await;
|
||||
modify_reputation(reputation, ctx.sender(), peer, BENEFIT_VALID_STATEMENT_FIRST).await;
|
||||
|
||||
let mut _span = handle_incoming_span.child("notify-backing");
|
||||
|
||||
@@ -1641,6 +1655,7 @@ async fn handle_network_update<Context, R>(
|
||||
metrics: &Metrics,
|
||||
runtime: &mut RuntimeInfo,
|
||||
rng: &mut R,
|
||||
reputation: &mut ReputationAggregator,
|
||||
) where
|
||||
R: rand::Rng,
|
||||
{
|
||||
@@ -1713,6 +1728,7 @@ async fn handle_network_update<Context, R>(
|
||||
metrics,
|
||||
runtime,
|
||||
rng,
|
||||
reputation,
|
||||
)
|
||||
.await;
|
||||
},
|
||||
@@ -1750,10 +1766,27 @@ impl<R: rand::Rng> StatementDistributionSubsystem<R> {
|
||||
metrics: Metrics,
|
||||
rng: R,
|
||||
) -> Self {
|
||||
Self { keystore, req_receiver: Some(req_receiver), metrics, rng }
|
||||
Self {
|
||||
keystore,
|
||||
req_receiver: Some(req_receiver),
|
||||
metrics,
|
||||
rng,
|
||||
reputation: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run<Context>(mut self, mut ctx: Context) -> std::result::Result<(), FatalError> {
|
||||
async fn run<Context>(self, ctx: Context) -> std::result::Result<(), FatalError> {
|
||||
self.run_inner(ctx, REPUTATION_CHANGE_INTERVAL).await
|
||||
}
|
||||
|
||||
async fn run_inner<Context>(
|
||||
mut self,
|
||||
mut ctx: Context,
|
||||
reputation_interval: Duration,
|
||||
) -> std::result::Result<(), FatalError> {
|
||||
let new_reputation_delay = || futures_timer::Delay::new(reputation_interval).fuse();
|
||||
let mut reputation_delay = new_reputation_delay();
|
||||
|
||||
let mut peers: HashMap<PeerId, PeerData> = HashMap::new();
|
||||
let mut topology_storage: SessionBoundGridTopologyStorage = Default::default();
|
||||
let mut authorities: HashMap<AuthorityDiscoveryId, PeerId> = HashMap::new();
|
||||
@@ -1778,55 +1811,61 @@ impl<R: rand::Rng> StatementDistributionSubsystem<R> {
|
||||
.map_err(FatalError::SpawnTask)?;
|
||||
|
||||
loop {
|
||||
let message =
|
||||
MuxedMessage::receive(&mut ctx, &mut req_receiver, &mut res_receiver).await;
|
||||
match message {
|
||||
MuxedMessage::Subsystem(result) => {
|
||||
let result = self
|
||||
.handle_subsystem_message(
|
||||
&mut ctx,
|
||||
&mut runtime,
|
||||
&mut peers,
|
||||
&mut topology_storage,
|
||||
&mut authorities,
|
||||
&mut active_heads,
|
||||
&mut recent_outdated_heads,
|
||||
&req_sender,
|
||||
result?,
|
||||
)
|
||||
.await;
|
||||
match result.into_nested()? {
|
||||
Ok(true) => break,
|
||||
Ok(false) => {},
|
||||
Err(jfyi) => gum::debug!(target: LOG_TARGET, error = ?jfyi),
|
||||
}
|
||||
select! {
|
||||
_ = reputation_delay => {
|
||||
self.reputation.send(ctx.sender()).await;
|
||||
reputation_delay = new_reputation_delay();
|
||||
},
|
||||
MuxedMessage::Requester(result) => {
|
||||
let result = self
|
||||
.handle_requester_message(
|
||||
&mut ctx,
|
||||
&topology_storage,
|
||||
&mut peers,
|
||||
&mut active_heads,
|
||||
&recent_outdated_heads,
|
||||
&req_sender,
|
||||
&mut runtime,
|
||||
result.ok_or(FatalError::RequesterReceiverFinished)?,
|
||||
)
|
||||
.await;
|
||||
log_error(result.map_err(From::from), "handle_requester_message")?;
|
||||
},
|
||||
MuxedMessage::Responder(result) => {
|
||||
let result = self
|
||||
.handle_responder_message(
|
||||
&peers,
|
||||
&mut active_heads,
|
||||
result.ok_or(FatalError::ResponderReceiverFinished)?,
|
||||
)
|
||||
.await;
|
||||
log_error(result.map_err(From::from), "handle_responder_message")?;
|
||||
},
|
||||
};
|
||||
message = MuxedMessage::receive(&mut ctx, &mut req_receiver, &mut res_receiver).fuse() => {
|
||||
match message {
|
||||
MuxedMessage::Subsystem(result) => {
|
||||
let result = self
|
||||
.handle_subsystem_message(
|
||||
&mut ctx,
|
||||
&mut runtime,
|
||||
&mut peers,
|
||||
&mut topology_storage,
|
||||
&mut authorities,
|
||||
&mut active_heads,
|
||||
&mut recent_outdated_heads,
|
||||
&req_sender,
|
||||
result?,
|
||||
)
|
||||
.await;
|
||||
match result.into_nested()? {
|
||||
Ok(true) => break,
|
||||
Ok(false) => {},
|
||||
Err(jfyi) => gum::debug!(target: LOG_TARGET, error = ?jfyi),
|
||||
}
|
||||
},
|
||||
MuxedMessage::Requester(result) => {
|
||||
let result = self
|
||||
.handle_requester_message(
|
||||
&mut ctx,
|
||||
&topology_storage,
|
||||
&mut peers,
|
||||
&mut active_heads,
|
||||
&recent_outdated_heads,
|
||||
&req_sender,
|
||||
&mut runtime,
|
||||
result.ok_or(FatalError::RequesterReceiverFinished)?,
|
||||
)
|
||||
.await;
|
||||
log_error(result.map_err(From::from), "handle_requester_message")?;
|
||||
},
|
||||
MuxedMessage::Responder(result) => {
|
||||
let result = self
|
||||
.handle_responder_message(
|
||||
&peers,
|
||||
&mut active_heads,
|
||||
result.ok_or(FatalError::ResponderReceiverFinished)?,
|
||||
)
|
||||
.await;
|
||||
log_error(result.map_err(From::from), "handle_responder_message")?;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1890,9 +1929,16 @@ impl<R: rand::Rng> StatementDistributionSubsystem<R> {
|
||||
bad_peers,
|
||||
} => {
|
||||
for bad in bad_peers {
|
||||
report_peer(ctx.sender(), bad, COST_FETCH_FAIL).await;
|
||||
modify_reputation(&mut self.reputation, ctx.sender(), bad, COST_FETCH_FAIL)
|
||||
.await;
|
||||
}
|
||||
report_peer(ctx.sender(), from_peer, BENEFIT_VALID_RESPONSE).await;
|
||||
modify_reputation(
|
||||
&mut self.reputation,
|
||||
ctx.sender(),
|
||||
from_peer,
|
||||
BENEFIT_VALID_RESPONSE,
|
||||
)
|
||||
.await;
|
||||
|
||||
let active_head = active_heads
|
||||
.get_mut(&relay_parent)
|
||||
@@ -1932,6 +1978,7 @@ impl<R: rand::Rng> StatementDistributionSubsystem<R> {
|
||||
&self.metrics,
|
||||
runtime,
|
||||
&mut self.rng,
|
||||
&mut self.reputation,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -1975,7 +2022,8 @@ impl<R: rand::Rng> StatementDistributionSubsystem<R> {
|
||||
}
|
||||
}
|
||||
},
|
||||
RequesterMessage::ReportPeer(peer, rep) => report_peer(ctx.sender(), peer, rep).await,
|
||||
RequesterMessage::ReportPeer(peer, rep) =>
|
||||
modify_reputation(&mut self.reputation, ctx.sender(), peer, rep).await,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2113,6 +2161,7 @@ impl<R: rand::Rng> StatementDistributionSubsystem<R> {
|
||||
metrics,
|
||||
runtime,
|
||||
&mut self.rng,
|
||||
&mut self.reputation,
|
||||
)
|
||||
.await;
|
||||
},
|
||||
|
||||
@@ -31,7 +31,9 @@ use polkadot_node_network_protocol::{
|
||||
use polkadot_node_primitives::{Statement, UncheckedSignedFullStatement};
|
||||
use polkadot_node_subsystem::{
|
||||
jaeger,
|
||||
messages::{network_bridge_event, AllMessages, RuntimeApiMessage, RuntimeApiRequest},
|
||||
messages::{
|
||||
network_bridge_event, AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
},
|
||||
ActivatedLeaf, LeafStatus,
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers::mock::make_ferdie_keystore;
|
||||
@@ -47,6 +49,7 @@ use sp_authority_discovery::AuthorityPair;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use sp_keystore::{Keystore, KeystorePtr};
|
||||
use std::{iter::FromIterator as _, sync::Arc, time::Duration};
|
||||
use util::reputation::add_reputation;
|
||||
|
||||
// Some deterministic genesis hash for protocol names
|
||||
const GENESIS_HASH: Hash = Hash::repeat_byte(0xff);
|
||||
@@ -733,12 +736,13 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
|
||||
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
|
||||
let bg = async move {
|
||||
let s = StatementDistributionSubsystem::new(
|
||||
Arc::new(LocalKeystore::in_memory()),
|
||||
statement_req_receiver,
|
||||
Default::default(),
|
||||
AlwaysZeroRng,
|
||||
);
|
||||
let s = StatementDistributionSubsystem {
|
||||
keystore: Arc::new(LocalKeystore::in_memory()),
|
||||
req_receiver: Some(statement_req_receiver),
|
||||
metrics: Default::default(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
};
|
||||
s.run(ctx).await.unwrap();
|
||||
};
|
||||
|
||||
@@ -862,8 +866,8 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(p, r)
|
||||
) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST => {}
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => {}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
@@ -936,12 +940,13 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
|
||||
let bg = async move {
|
||||
let s = StatementDistributionSubsystem::new(
|
||||
make_ferdie_keystore(),
|
||||
statement_req_receiver,
|
||||
Default::default(),
|
||||
AlwaysZeroRng,
|
||||
);
|
||||
let s = StatementDistributionSubsystem {
|
||||
keystore: make_ferdie_keystore(),
|
||||
req_receiver: Some(statement_req_receiver),
|
||||
metrics: Default::default(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
};
|
||||
s.run(ctx).await.unwrap();
|
||||
};
|
||||
|
||||
@@ -1226,8 +1231,8 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(p, r)
|
||||
) if p == peer_bad && r == COST_WRONG_HASH => {}
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) if p == peer_bad && r == COST_WRONG_HASH.into() => {}
|
||||
);
|
||||
|
||||
// a is tried again (retried in reverse order):
|
||||
@@ -1277,22 +1282,22 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(p, r)
|
||||
) if p == peer_a && r == COST_FETCH_FAIL => {}
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) if p == peer_a && r == COST_FETCH_FAIL.into() => {}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(p, r)
|
||||
) if p == peer_c && r == BENEFIT_VALID_RESPONSE => {}
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() => {}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(p, r)
|
||||
) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST => {}
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => {}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
@@ -1392,6 +1397,441 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
executor::block_on(future::join(test_fut, bg));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delay_reputation_changes() {
|
||||
sp_tracing::try_init_simple();
|
||||
let hash_a = Hash::repeat_byte(1);
|
||||
|
||||
let candidate = {
|
||||
let mut c = dummy_committed_candidate_receipt(dummy_hash());
|
||||
c.descriptor.relay_parent = hash_a;
|
||||
c.descriptor.para_id = 1.into();
|
||||
c.commitments.new_validation_code = Some(ValidationCode(vec![1, 2, 3]));
|
||||
c
|
||||
};
|
||||
|
||||
let peer_a = PeerId::random(); // Alice
|
||||
let peer_b = PeerId::random(); // Bob
|
||||
let peer_c = PeerId::random(); // Charlie
|
||||
let peer_bad = PeerId::random(); // No validator
|
||||
|
||||
let validators = vec![
|
||||
Sr25519Keyring::Alice.pair(),
|
||||
Sr25519Keyring::Bob.pair(),
|
||||
Sr25519Keyring::Charlie.pair(),
|
||||
// We:
|
||||
Sr25519Keyring::Ferdie.pair(),
|
||||
];
|
||||
|
||||
let session_info = make_session_info(validators, vec![vec![0, 1, 2, 4], vec![3]]);
|
||||
|
||||
let session_index = 1;
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool);
|
||||
|
||||
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
|
||||
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
|
||||
let reputation_interval = Duration::from_millis(100);
|
||||
|
||||
let bg = async move {
|
||||
let s = StatementDistributionSubsystem {
|
||||
keystore: make_ferdie_keystore(),
|
||||
req_receiver: Some(statement_req_receiver),
|
||||
metrics: Default::default(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| false),
|
||||
};
|
||||
s.run_inner(ctx, reputation_interval).await.unwrap();
|
||||
};
|
||||
|
||||
let test_fut = async move {
|
||||
// register our active heads.
|
||||
handle
|
||||
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
|
||||
ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
hash: hash_a,
|
||||
number: 1,
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
}),
|
||||
)))
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(
|
||||
RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx))
|
||||
)
|
||||
if r == hash_a
|
||||
=> {
|
||||
let _ = tx.send(Ok(session_index));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(
|
||||
RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx))
|
||||
)
|
||||
if r == hash_a && sess_index == session_index
|
||||
=> {
|
||||
let _ = tx.send(Ok(Some(session_info)));
|
||||
}
|
||||
);
|
||||
|
||||
// notify of peers and view
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer_a.clone(),
|
||||
ObservedRole::Full,
|
||||
ValidationVersion::V1.into(),
|
||||
Some(HashSet::from([Sr25519Keyring::Alice.public().into()])),
|
||||
),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer_b.clone(),
|
||||
ObservedRole::Full,
|
||||
ValidationVersion::V1.into(),
|
||||
Some(HashSet::from([Sr25519Keyring::Bob.public().into()])),
|
||||
),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer_c.clone(),
|
||||
ObservedRole::Full,
|
||||
ValidationVersion::V1.into(),
|
||||
Some(HashSet::from([Sr25519Keyring::Charlie.public().into()])),
|
||||
),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer_bad.clone(),
|
||||
ObservedRole::Full,
|
||||
ValidationVersion::V1.into(),
|
||||
None,
|
||||
),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerViewChange(peer_a.clone(), view![hash_a]),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerViewChange(peer_b.clone(), view![hash_a]),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerViewChange(peer_c.clone(), view![hash_a]),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerViewChange(peer_bad.clone(), view![hash_a]),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
// receive a seconded statement from peer A, which does not provide the request data,
|
||||
// then get that data from peer C. It should be propagated onwards to peer B and to
|
||||
// candidate backing.
|
||||
let statement = {
|
||||
let signing_context = SigningContext { parent_hash: hash_a, session_index };
|
||||
|
||||
let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory());
|
||||
let alice_public = Keystore::sr25519_generate_new(
|
||||
&*keystore,
|
||||
ValidatorId::ID,
|
||||
Some(&Sr25519Keyring::Alice.to_seed()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
SignedFullStatement::sign(
|
||||
&keystore,
|
||||
Statement::Seconded(candidate.clone()),
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&alice_public.into(),
|
||||
)
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("should be signed")
|
||||
};
|
||||
|
||||
let metadata = derive_metadata_assuming_seconded(hash_a, statement.clone().into());
|
||||
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_a.clone(),
|
||||
Versioned::V1(protocol_v1::StatementDistributionMessage::LargeStatement(
|
||||
metadata.clone(),
|
||||
)),
|
||||
),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::SendRequests(
|
||||
mut reqs, IfDisconnected::ImmediateError
|
||||
)
|
||||
) => {
|
||||
let reqs = reqs.pop().unwrap();
|
||||
let outgoing = match reqs {
|
||||
Requests::StatementFetchingV1(outgoing) => outgoing,
|
||||
_ => panic!("Unexpected request"),
|
||||
};
|
||||
let req = outgoing.payload;
|
||||
assert_eq!(req.relay_parent, metadata.relay_parent);
|
||||
assert_eq!(req.candidate_hash, metadata.candidate_hash);
|
||||
assert_eq!(outgoing.peer, Recipient::Peer(peer_a));
|
||||
// Just drop request - should trigger error.
|
||||
}
|
||||
);
|
||||
|
||||
// There is a race between request handler asking for more peers and processing of the
|
||||
// coming `PeerMessage`s, we want the request handler to ask first here for better test
|
||||
// coverage:
|
||||
Delay::new(Duration::from_millis(20)).await;
|
||||
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_c.clone(),
|
||||
Versioned::V1(protocol_v1::StatementDistributionMessage::LargeStatement(
|
||||
metadata.clone(),
|
||||
)),
|
||||
),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
// Malicious peer:
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_bad.clone(),
|
||||
Versioned::V1(protocol_v1::StatementDistributionMessage::LargeStatement(
|
||||
metadata.clone(),
|
||||
)),
|
||||
),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
// Let c fail once too:
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::SendRequests(
|
||||
mut reqs, IfDisconnected::ImmediateError
|
||||
)
|
||||
) => {
|
||||
let reqs = reqs.pop().unwrap();
|
||||
let outgoing = match reqs {
|
||||
Requests::StatementFetchingV1(outgoing) => outgoing,
|
||||
_ => panic!("Unexpected request"),
|
||||
};
|
||||
let req = outgoing.payload;
|
||||
assert_eq!(req.relay_parent, metadata.relay_parent);
|
||||
assert_eq!(req.candidate_hash, metadata.candidate_hash);
|
||||
assert_eq!(outgoing.peer, Recipient::Peer(peer_c));
|
||||
}
|
||||
);
|
||||
|
||||
// a fails again:
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::SendRequests(
|
||||
mut reqs, IfDisconnected::ImmediateError
|
||||
)
|
||||
) => {
|
||||
let reqs = reqs.pop().unwrap();
|
||||
let outgoing = match reqs {
|
||||
Requests::StatementFetchingV1(outgoing) => outgoing,
|
||||
_ => panic!("Unexpected request"),
|
||||
};
|
||||
let req = outgoing.payload;
|
||||
assert_eq!(req.relay_parent, metadata.relay_parent);
|
||||
assert_eq!(req.candidate_hash, metadata.candidate_hash);
|
||||
// On retry, we should have reverse order:
|
||||
assert_eq!(outgoing.peer, Recipient::Peer(peer_a));
|
||||
}
|
||||
);
|
||||
|
||||
// Send invalid response (all other peers have been tried now):
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::SendRequests(
|
||||
mut reqs, IfDisconnected::ImmediateError
|
||||
)
|
||||
) => {
|
||||
let reqs = reqs.pop().unwrap();
|
||||
let outgoing = match reqs {
|
||||
Requests::StatementFetchingV1(outgoing) => outgoing,
|
||||
_ => panic!("Unexpected request"),
|
||||
};
|
||||
let req = outgoing.payload;
|
||||
assert_eq!(req.relay_parent, metadata.relay_parent);
|
||||
assert_eq!(req.candidate_hash, metadata.candidate_hash);
|
||||
assert_eq!(outgoing.peer, Recipient::Peer(peer_bad));
|
||||
let bad_candidate = {
|
||||
let mut bad = candidate.clone();
|
||||
bad.descriptor.para_id = 0xeadbeaf.into();
|
||||
bad
|
||||
};
|
||||
let response = StatementFetchingResponse::Statement(bad_candidate);
|
||||
outgoing.pending_response.send(Ok(response.encode())).unwrap();
|
||||
}
|
||||
);
|
||||
|
||||
// a is tried again (retried in reverse order):
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::SendRequests(
|
||||
mut reqs, IfDisconnected::ImmediateError
|
||||
)
|
||||
) => {
|
||||
let reqs = reqs.pop().unwrap();
|
||||
let outgoing = match reqs {
|
||||
Requests::StatementFetchingV1(outgoing) => outgoing,
|
||||
_ => panic!("Unexpected request"),
|
||||
};
|
||||
let req = outgoing.payload;
|
||||
assert_eq!(req.relay_parent, metadata.relay_parent);
|
||||
assert_eq!(req.candidate_hash, metadata.candidate_hash);
|
||||
// On retry, we should have reverse order:
|
||||
assert_eq!(outgoing.peer, Recipient::Peer(peer_a));
|
||||
}
|
||||
);
|
||||
|
||||
// c succeeds now:
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::SendRequests(
|
||||
mut reqs, IfDisconnected::ImmediateError
|
||||
)
|
||||
) => {
|
||||
let reqs = reqs.pop().unwrap();
|
||||
let outgoing = match reqs {
|
||||
Requests::StatementFetchingV1(outgoing) => outgoing,
|
||||
_ => panic!("Unexpected request"),
|
||||
};
|
||||
let req = outgoing.payload;
|
||||
assert_eq!(req.relay_parent, metadata.relay_parent);
|
||||
assert_eq!(req.candidate_hash, metadata.candidate_hash);
|
||||
// On retry, we should have reverse order:
|
||||
assert_eq!(outgoing.peer, Recipient::Peer(peer_c));
|
||||
let response = StatementFetchingResponse::Statement(candidate.clone());
|
||||
outgoing.pending_response.send(Ok(response.encode())).unwrap();
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::CandidateBacking(
|
||||
CandidateBackingMessage::Statement(r, s)
|
||||
) if r == hash_a && s == statement => {}
|
||||
);
|
||||
|
||||
// Now messages should go out:
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::SendValidationMessage(
|
||||
mut recipients,
|
||||
Versioned::V1(protocol_v1::ValidationProtocol::StatementDistribution(
|
||||
protocol_v1::StatementDistributionMessage::LargeStatement(meta)
|
||||
)),
|
||||
)
|
||||
) => {
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
?recipients,
|
||||
"Recipients received"
|
||||
);
|
||||
recipients.sort();
|
||||
let mut expected = vec![peer_b, peer_c, peer_bad];
|
||||
expected.sort();
|
||||
assert_eq!(recipients, expected);
|
||||
assert_eq!(meta.relay_parent, hash_a);
|
||||
assert_eq!(meta.candidate_hash, statement.payload().candidate_hash());
|
||||
assert_eq!(meta.signed_by, statement.validator_index());
|
||||
assert_eq!(&meta.signature, statement.signature());
|
||||
}
|
||||
);
|
||||
|
||||
// Wait enough to fire reputation delay
|
||||
futures_timer::Delay::new(reputation_interval).await;
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Batch(v))
|
||||
) => {
|
||||
let mut expected_change = HashMap::new();
|
||||
for rep in vec![COST_FETCH_FAIL, BENEFIT_VALID_STATEMENT_FIRST] {
|
||||
add_reputation(&mut expected_change, peer_a, rep)
|
||||
}
|
||||
for rep in vec![BENEFIT_VALID_RESPONSE, BENEFIT_VALID_STATEMENT] {
|
||||
add_reputation(&mut expected_change, peer_c, rep)
|
||||
}
|
||||
for rep in vec![COST_WRONG_HASH, BENEFIT_VALID_STATEMENT] {
|
||||
add_reputation(&mut expected_change, peer_bad, rep)
|
||||
}
|
||||
assert_eq!(v, expected_change);
|
||||
}
|
||||
);
|
||||
|
||||
handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
|
||||
};
|
||||
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(bg);
|
||||
|
||||
executor::block_on(future::join(test_fut, bg));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn share_prioritizes_backing_group() {
|
||||
sp_tracing::try_init_simple();
|
||||
@@ -1448,12 +1888,13 @@ fn share_prioritizes_backing_group() {
|
||||
IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
|
||||
let bg = async move {
|
||||
let s = StatementDistributionSubsystem::new(
|
||||
make_ferdie_keystore(),
|
||||
statement_req_receiver,
|
||||
Default::default(),
|
||||
AlwaysZeroRng,
|
||||
);
|
||||
let s = StatementDistributionSubsystem {
|
||||
keystore: make_ferdie_keystore(),
|
||||
req_receiver: Some(statement_req_receiver),
|
||||
metrics: Default::default(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
};
|
||||
s.run(ctx).await.unwrap();
|
||||
};
|
||||
|
||||
@@ -1741,12 +2182,13 @@ fn peer_cant_flood_with_large_statements() {
|
||||
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
|
||||
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
let bg = async move {
|
||||
let s = StatementDistributionSubsystem::new(
|
||||
make_ferdie_keystore(),
|
||||
statement_req_receiver,
|
||||
Default::default(),
|
||||
AlwaysZeroRng,
|
||||
);
|
||||
let s = StatementDistributionSubsystem {
|
||||
keystore: make_ferdie_keystore(),
|
||||
req_receiver: Some(statement_req_receiver),
|
||||
metrics: Default::default(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
};
|
||||
s.run(ctx).await.unwrap();
|
||||
};
|
||||
|
||||
@@ -1873,9 +2315,9 @@ fn peer_cant_flood_with_large_statements() {
|
||||
requested = true;
|
||||
},
|
||||
|
||||
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(p, r))
|
||||
if p == peer_a && r == COST_APPARENT_FLOOD =>
|
||||
{
|
||||
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(
|
||||
ReportPeerMessage::Single(p, r),
|
||||
)) if p == peer_a && r == COST_APPARENT_FLOOD.into() => {
|
||||
punished = true;
|
||||
},
|
||||
|
||||
@@ -1945,12 +2387,13 @@ fn handle_multiple_seconded_statements() {
|
||||
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
|
||||
let virtual_overseer_fut = async move {
|
||||
let s = StatementDistributionSubsystem::new(
|
||||
Arc::new(LocalKeystore::in_memory()),
|
||||
statement_req_receiver,
|
||||
Default::default(),
|
||||
AlwaysZeroRng,
|
||||
);
|
||||
let s = StatementDistributionSubsystem {
|
||||
keystore: Arc::new(LocalKeystore::in_memory()),
|
||||
req_receiver: Some(statement_req_receiver),
|
||||
metrics: Default::default(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
};
|
||||
s.run(ctx).await.unwrap();
|
||||
};
|
||||
|
||||
@@ -2136,10 +2579,10 @@ fn handle_multiple_seconded_statements() {
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(p, r)
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) => {
|
||||
assert_eq!(p, peer_a);
|
||||
assert_eq!(r, BENEFIT_VALID_STATEMENT_FIRST);
|
||||
assert_eq!(r, BENEFIT_VALID_STATEMENT_FIRST.into());
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2188,10 +2631,10 @@ fn handle_multiple_seconded_statements() {
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(p, r)
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) => {
|
||||
assert_eq!(p, peer_b);
|
||||
assert_eq!(r, BENEFIT_VALID_STATEMENT);
|
||||
assert_eq!(r, BENEFIT_VALID_STATEMENT.into());
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2237,10 +2680,10 @@ fn handle_multiple_seconded_statements() {
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(p, r)
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) => {
|
||||
assert_eq!(p, peer_a);
|
||||
assert_eq!(r, BENEFIT_VALID_STATEMENT_FIRST);
|
||||
assert_eq!(r, BENEFIT_VALID_STATEMENT_FIRST.into());
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2290,10 +2733,10 @@ fn handle_multiple_seconded_statements() {
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(p, r)
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) => {
|
||||
assert_eq!(p, peer_b);
|
||||
assert_eq!(r, BENEFIT_VALID_STATEMENT);
|
||||
assert_eq!(r, BENEFIT_VALID_STATEMENT.into());
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user