use core::convert::Infallible;
use core::time::Duration;
use crossbeam_channel::Receiver;
use retry::delay::Fibonacci;
use retry::retry_with_index;
use tracing::{debug, debug_span, error_span, trace, warn};
use ibc_relayer_types::core::ics02_client::events::UpdateClient;
use ibc_relayer_types::events::IbcEvent;
use crate::util::retry::clamp_total;
use crate::util::task::{spawn_background_task, Next, TaskError, TaskHandle};
use crate::{
chain::handle::ChainHandle,
foreign_client::{ForeignClient, MisbehaviourResults},
};
use super::WorkerCmd;
const REFRESH_CHECK_INTERVAL: Duration = Duration::from_secs(5); const INITIAL_BACKOFF: Duration = Duration::from_secs(5); const MAX_REFRESH_DELAY: Duration = Duration::from_secs(60 * 60); const MAX_REFRESH_TOTAL_DELAY: Duration = Duration::from_secs(60 * 60 * 24);
pub fn spawn_refresh_client<ChainA: ChainHandle, ChainB: ChainHandle>(
mut client: ForeignClient<ChainA, ChainB>,
) -> Option<TaskHandle> {
if client.is_expired_or_frozen() {
warn!(
client = %client.id,
"skipping refresh client task on frozen client",
);
return None;
}
Some(spawn_background_task(
error_span!(
"worker.client.refresh",
client = %client.id,
src_chain = %client.src_chain.id(),
dst_chain = %client.dst_chain.id(),
),
Some(REFRESH_CHECK_INTERVAL),
move || {
let res = retry_with_index(refresh_strategy(), |_| client.refresh());
match res {
Ok(_) => Ok(Next::Continue),
Err(e) => Err(TaskError::Fatal(Box::new(e))),
}
},
))
}
pub fn detect_misbehavior_task<ChainA: ChainHandle, ChainB: ChainHandle>(
receiver: Receiver<WorkerCmd>,
client: ForeignClient<ChainB, ChainA>,
) -> Option<TaskHandle> {
if client.is_expired_or_frozen() {
warn!(
client = %client.id(),
src_chain = %client.src_chain.id(),
dst_chain = %client.dst_chain.id(),
"skipping detect misbehavior task on frozen client",
);
return None;
}
let mut initial_check_done = false;
let handle = spawn_background_task(
error_span!(
"worker.client.misbehaviour",
client = %client.id,
src_chain = %client.src_chain.id(),
dst_chain = %client.dst_chain.id(),
),
Some(Duration::from_millis(600)),
move || -> Result<Next, TaskError<Infallible>> {
if !initial_check_done {
initial_check_done = true;
debug!("doing initial misbehavior check");
let result = client.detect_misbehaviour_and_submit_evidence(None);
debug!("misbehavior detection result: {:?}", result);
}
if let Ok(WorkerCmd::IbcEvents { batch }) = receiver.try_recv() {
trace!("received batch: {:?}", batch);
for event_with_height in batch.events {
if let IbcEvent::UpdateClient(update) = event_with_height.event {
match on_client_update(&client, update) {
Next::Continue => continue,
Next::Abort => return Ok(Next::Abort),
}
}
}
}
Ok(Next::Continue)
},
);
Some(handle)
}
fn on_client_update<ChainA: ChainHandle, ChainB: ChainHandle>(
client: &ForeignClient<ChainB, ChainA>,
update: UpdateClient,
) -> Next {
let _span = debug_span!(
"on_client_update",
client = %update.client_id(),
client_type = %update.client_type(),
height = %update.consensus_height(),
);
debug!("checking misbehavior for updated client");
let result = client.detect_misbehaviour_and_submit_evidence(Some(update));
trace!("misbehavior detection result: {:?}", result);
match result {
MisbehaviourResults::ValidClient => {
debug!("client is valid");
Next::Continue
}
MisbehaviourResults::VerificationError => {
debug!("client verification error, will retry in next call");
Next::Continue
}
MisbehaviourResults::EvidenceSubmitted(_) => {
debug!("misbehavior detected! Evidence successfully submitted, exiting");
Next::Abort
}
MisbehaviourResults::CannotExecute => {
debug!("cannot execute misbehavior detection, exiting");
Next::Abort
}
}
}
fn refresh_strategy() -> impl Iterator<Item = Duration> {
clamp_total(
Fibonacci::from(INITIAL_BACKOFF),
MAX_REFRESH_DELAY,
MAX_REFRESH_TOTAL_DELAY,
)
}