use codec::Encode;
use futures::channel::{oneshot, oneshot::Canceled};
use log::{debug, warn};
use parking_lot::Mutex;
use sc_network::{
request_responses::{IfDisconnected, RequestFailure},
NetworkRequest, PeerId, ProtocolName,
};
use sp_consensus_beefy::{ecdsa_crypto::AuthorityId, ValidatorSet};
use sp_runtime::traits::{Block, NumberFor};
use std::{collections::VecDeque, result::Result, sync::Arc};
use crate::{
communication::{
benefit, cost,
peers::PeerReport,
request_response::{Error, JustificationRequest, BEEFY_SYNC_LOG_TARGET},
},
justification::{decode_and_verify_finality_proof, BeefyVersionedFinalityProof},
metric_inc,
metrics::{register_metrics, OnDemandOutgoingRequestsMetrics},
KnownPeers,
};
type Response = Result<Vec<u8>, RequestFailure>;
type ResponseReceiver = oneshot::Receiver<Response>;
#[derive(Clone, Debug)]
struct RequestInfo<B: Block> {
block: NumberFor<B>,
active_set: ValidatorSet<AuthorityId>,
}
enum State<B: Block> {
Idle,
AwaitingResponse(PeerId, RequestInfo<B>, ResponseReceiver),
}
pub(crate) enum ResponseInfo<B: Block> {
Pending,
ValidProof(BeefyVersionedFinalityProof<B>, PeerReport),
PeerReport(PeerReport),
}
pub struct OnDemandJustificationsEngine<B: Block> {
network: Arc<dyn NetworkRequest + Send + Sync>,
protocol_name: ProtocolName,
live_peers: Arc<Mutex<KnownPeers<B>>>,
peers_cache: VecDeque<PeerId>,
state: State<B>,
metrics: Option<OnDemandOutgoingRequestsMetrics>,
}
impl<B: Block> OnDemandJustificationsEngine<B> {
pub fn new(
network: Arc<dyn NetworkRequest + Send + Sync>,
protocol_name: ProtocolName,
live_peers: Arc<Mutex<KnownPeers<B>>>,
prometheus_registry: Option<prometheus::Registry>,
) -> Self {
let metrics = register_metrics(prometheus_registry);
Self {
network,
protocol_name,
live_peers,
peers_cache: VecDeque::new(),
state: State::Idle,
metrics,
}
}
fn reset_peers_cache_for_block(&mut self, block: NumberFor<B>) {
self.peers_cache = self.live_peers.lock().further_than(block);
}
fn try_next_peer(&mut self) -> Option<PeerId> {
let live = self.live_peers.lock();
while let Some(peer) = self.peers_cache.pop_front() {
if live.contains(&peer) {
return Some(peer)
}
}
None
}
fn request_from_peer(&mut self, peer: PeerId, req_info: RequestInfo<B>) {
debug!(
target: BEEFY_SYNC_LOG_TARGET,
"🥩 requesting justif #{:?} from peer {:?}", req_info.block, peer,
);
let payload = JustificationRequest::<B> { begin: req_info.block }.encode();
let (tx, rx) = oneshot::channel();
self.network.start_request(
peer,
self.protocol_name.clone(),
payload,
tx,
IfDisconnected::ImmediateError,
);
self.state = State::AwaitingResponse(peer, req_info, rx);
}
pub fn request(&mut self, block: NumberFor<B>, active_set: ValidatorSet<AuthorityId>) {
if matches!(self.state, State::AwaitingResponse(_, _, _)) {
return
}
self.reset_peers_cache_for_block(block);
if let Some(peer) = self.try_next_peer() {
self.request_from_peer(peer, RequestInfo { block, active_set });
} else {
metric_inc!(self, beefy_on_demand_justification_no_peer_to_request_from);
debug!(
target: BEEFY_SYNC_LOG_TARGET,
"🥩 no good peers to request justif #{:?} from", block
);
}
}
pub fn cancel_requests_older_than(&mut self, block: NumberFor<B>) {
match &self.state {
State::AwaitingResponse(_, req_info, _) if req_info.block <= block => {
debug!(
target: BEEFY_SYNC_LOG_TARGET,
"🥩 cancel pending request for justification #{:?}", req_info.block
);
self.state = State::Idle;
},
_ => (),
}
}
fn process_response(
&mut self,
peer: &PeerId,
req_info: &RequestInfo<B>,
response: Result<Response, Canceled>,
) -> Result<BeefyVersionedFinalityProof<B>, Error> {
response
.map_err(|e| {
debug!(
target: BEEFY_SYNC_LOG_TARGET,
"🥩 on-demand sc-network channel sender closed, err: {:?}", e
);
Error::ResponseError
})?
.map_err(|e| {
debug!(
target: BEEFY_SYNC_LOG_TARGET,
"🥩 for on demand justification #{:?}, peer {:?} error: {:?}",
req_info.block,
peer,
e
);
match e {
RequestFailure::Refused => {
metric_inc!(self, beefy_on_demand_justification_peer_refused);
let peer_report =
PeerReport { who: *peer, cost_benefit: cost::REFUSAL_RESPONSE };
Error::InvalidResponse(peer_report)
},
_ => {
metric_inc!(self, beefy_on_demand_justification_peer_error);
Error::ResponseError
},
}
})
.and_then(|encoded| {
decode_and_verify_finality_proof::<B>(
&encoded[..],
req_info.block,
&req_info.active_set,
)
.map_err(|(err, signatures_checked)| {
metric_inc!(self, beefy_on_demand_justification_invalid_proof);
debug!(
target: BEEFY_SYNC_LOG_TARGET,
"🥩 for on demand justification #{:?}, peer {:?} responded with invalid proof: {:?}",
req_info.block, peer, err
);
let mut cost = cost::INVALID_PROOF;
cost.value +=
cost::PER_SIGNATURE_CHECKED.saturating_mul(signatures_checked as i32);
Error::InvalidResponse(PeerReport { who: *peer, cost_benefit: cost })
})
})
}
pub(crate) async fn next(&mut self) -> ResponseInfo<B> {
let (peer, req_info, resp) = match &mut self.state {
State::Idle => {
futures::future::pending::<()>().await;
return ResponseInfo::Pending
},
State::AwaitingResponse(peer, req_info, receiver) => {
let resp = receiver.await;
(*peer, req_info.clone(), resp)
},
};
self.state = State::Idle;
let block = req_info.block;
match self.process_response(&peer, &req_info, resp) {
Err(err) => {
if let Some(peer) = self.try_next_peer() {
self.request_from_peer(peer, req_info);
} else {
warn!(
target: BEEFY_SYNC_LOG_TARGET,
"🥩 ran out of peers to request justif #{:?} from", block
);
}
if let Error::InvalidResponse(peer_report) = err {
ResponseInfo::PeerReport(peer_report)
} else {
ResponseInfo::Pending
}
},
Ok(proof) => {
metric_inc!(self, beefy_on_demand_justification_good_proof);
debug!(
target: BEEFY_SYNC_LOG_TARGET,
"🥩 received valid on-demand justif #{:?} from {:?}", block, peer
);
let peer_report = PeerReport { who: peer, cost_benefit: benefit::VALIDATED_PROOF };
ResponseInfo::ValidProof(proof, peer_report)
},
}
}
}